diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f5fc7e1e..7ba584fde8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### Changes +- Removed deprecated ReactNativeTracing option `idleTimeout` use `idleTimeoutMs` instead ([#3998](https://github.com/getsentry/sentry-react-native/pull/3998)) +- Removed deprecated ReactNativeTracing option `maxTransactionDuration` use `finalTimeoutMs` instead ([#3998](https://github.com/getsentry/sentry-react-native/pull/3998)) +- Removed `beforeNavigate` use `beforeStartSpan` instead ([#3998](https://github.com/getsentry/sentry-react-native/pull/3998)) + - `beforeStartSpan` is executed before the span start, compared to `beforeNavigate` which was executed before the navigation ended (after the span was created) - New Native Frames Integration ([#3996](https://github.com/getsentry/sentry-react-native/pull/3996)) - New Stall Tracking Integration ([#3997](https://github.com/getsentry/sentry-react-native/pull/3997)) - New User Interaction Tracing Integration ([#3999](https://github.com/getsentry/sentry-react-native/pull/3999)) @@ -22,6 +26,12 @@ enableStallTracking: true, // default true enableUserInteractionTracing: true, // default false integrations: [ + Sentry.reactNativeTracingIntegration({ + beforeStartSpan: (startSpanOptions) => { + startSpanOptions.name = 'New Name'; + return startSpanOptions; + }, + }), Sentry.appStartIntegration({ standalone: false, // default false }), diff --git a/samples/expo/app/_layout.tsx b/samples/expo/app/_layout.tsx index 126a499b66..539dbb0fad 100644 --- a/samples/expo/app/_layout.tsx +++ b/samples/expo/app/_layout.tsx @@ -54,7 +54,7 @@ process.env.EXPO_SKIP_DURING_EXPORT !== 'true' && Sentry.init({ // default: [/.*/] failedRequestTargets: [/.*/], }), - new Sentry.ReactNativeTracing({ + Sentry.reactNativeTracingIntegration({ routingInstrumentation, }), ); diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx index bbb71ec348..167b404766 100644 --- a/samples/react-native/src/App.tsx +++ b/samples/react-native/src/App.tsx @@ -66,11 +66,10 @@ Sentry.init({ }, integrations(integrations) { integrations.push( - new Sentry.ReactNativeTracing({ + Sentry.reactNativeTracingIntegration({ // The time to wait in ms until the transaction will be finished, For testing, default is 1000 ms - idleTimeout: 5000, + idleTimeoutMs: 5_000, routingInstrumentation: reactNavigationInstrumentation, - enableUserInteractionTracing: true, ignoreEmptyBackNavigationTransactions: true, }), Sentry.httpClientIntegration({ diff --git a/src/js/client.ts b/src/js/client.ts index 8d2d66a09b..b12ec9a722 100644 --- a/src/js/client.ts +++ b/src/js/client.ts @@ -19,7 +19,7 @@ import { defaultSdkInfo } from './integrations/sdkinfo'; import type { ReactNativeClientOptions } from './options'; import type { mobileReplayIntegration } from './replay/mobilereplay'; import { MOBILE_REPLAY_INTEGRATION_NAME } from './replay/mobilereplay'; -import type { ReactNativeTracing } from './tracing'; +import { getReactNativeTracingIntegration } from './tracing/reactnativetracing'; import { createUserFeedbackEnvelope, items } from './utils/envelope'; import { ignoreRequireCycleLogs } from './utils/ignorerequirecyclelogs'; import { mergeOutcomes } from './utils/outcome'; @@ -141,7 +141,7 @@ export class ReactNativeClient extends BaseClient { */ protected _setupIntegrations(): void { super._setupIntegrations(); - const tracing = this.getIntegrationByName('ReactNativeTracing'); + const tracing = getReactNativeTracingIntegration(this); const routingName = tracing?.options?.routingInstrumentation?.name; if (routingName) { this.addIntegration(createIntegration(routingName)); diff --git a/src/js/index.ts b/src/js/index.ts index 79ca02795d..8854f73716 100644 --- a/src/js/index.ts +++ b/src/js/index.ts @@ -58,7 +58,7 @@ export { init, wrap, nativeCrash, flush, close, captureUserFeedback, withScope } export { TouchEventBoundary, withTouchEventBoundary } from './touchevents'; export { - ReactNativeTracing, + reactNativeTracingIntegration, ReactNavigationV5Instrumentation, ReactNavigationInstrumentation, ReactNativeNavigationInstrumentation, diff --git a/src/js/integrations/default.ts b/src/js/integrations/default.ts index 7e1164c4a2..06ad272a70 100644 --- a/src/js/integrations/default.ts +++ b/src/js/integrations/default.ts @@ -3,7 +3,7 @@ import type { BrowserOptions } from '@sentry/react'; import type { Integration } from '@sentry/types'; import type { ReactNativeClientOptions } from '../options'; -import { ReactNativeTracing } from '../tracing'; +import { reactNativeTracingIntegration } from '../tracing'; import { isExpoGo, notWeb } from '../utils/environment'; import { appStartIntegration, @@ -114,10 +114,7 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ integrations.push(userInteractionIntegration()); } if (hasTracingEnabled && options.enableAutoPerformanceTracing) { - integrations.push(new ReactNativeTracing()); - } - if (hasTracingEnabled && options.enableAppStartTracking) { - integrations.push(appStartIntegration()); + integrations.push(reactNativeTracingIntegration()); } if (options.enableCaptureFailedRequests) { integrations.push(httpClientIntegration()); diff --git a/src/js/touchevents.tsx b/src/js/touchevents.tsx index 45908a9054..06ab4b5523 100644 --- a/src/js/touchevents.tsx +++ b/src/js/touchevents.tsx @@ -6,7 +6,6 @@ import type { GestureResponderEvent } from 'react-native'; import { StyleSheet, View } from 'react-native'; import { createIntegration } from './integrations/factory'; -import type { ReactNativeTracing } from './tracing'; import { startUserInteractionSpan } from './tracing/integrations/userInteraction'; import { UI_ACTION_TOUCH } from './tracing/ops'; @@ -92,17 +91,12 @@ class TouchEventBoundary extends React.Component { public readonly name: string = 'TouchEventBoundary'; - private _tracingIntegration: ReactNativeTracing | null = null; - /** * Registers the TouchEventBoundary as a Sentry Integration. */ public componentDidMount(): void { const client = getClient(); client?.addIntegration?.(createIntegration(this.name)); - if (!this._tracingIntegration && client) { - this._tracingIntegration = client.getIntegrationByName('ReactNativeTracing') || null; - } } /** diff --git a/src/js/tracing/index.ts b/src/js/tracing/index.ts index bcd0ebd8e7..dc071fe236 100644 --- a/src/js/tracing/index.ts +++ b/src/js/tracing/index.ts @@ -1,4 +1,8 @@ -export { ReactNativeTracing } from './reactnativetracing'; +export { + reactNativeTracingIntegration, + INTEGRATION_NAME as REACT_NATIVE_TRACING_INTEGRATION_NAME, +} from './reactnativetracing'; +export type { ReactNativeTracingIntegration } from './reactnativetracing'; export type { RoutingInstrumentationInstance } from './routingInstrumentation'; export { RoutingInstrumentation } from './routingInstrumentation'; diff --git a/src/js/tracing/integrations/appStart.ts b/src/js/tracing/integrations/appStart.ts index db7bd365b2..875507f9c5 100644 --- a/src/js/tracing/integrations/appStart.ts +++ b/src/js/tracing/integrations/appStart.ts @@ -22,7 +22,7 @@ import { APP_START_WARM as APP_START_WARM_OP, UI_LOAD as UI_LOAD_OP, } from '../ops'; -import { ReactNativeTracing } from '../reactnativetracing'; +import { getReactNativeTracingIntegration } from '../reactnativetracing'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP } from '../semanticAttributes'; import { createChildSpanJSON, createSpanJSON, getBundleStartTimestampMs } from '../utils'; @@ -126,8 +126,7 @@ export const appStartIntegration = ({ const afterAllSetup = (client: Client): void => { if (standaloneUserOption === undefined) { // If not user defined, set based on the routing instrumentation presence - standalone = !client.getIntegrationByName(ReactNativeTracing.id)?.options - .routingInstrumentation; + standalone = !getReactNativeTracingIntegration(client)?.options.routingInstrumentation; } }; diff --git a/src/js/tracing/integrations/userInteraction.ts b/src/js/tracing/integrations/userInteraction.ts index 507e99b90c..b187f1b306 100644 --- a/src/js/tracing/integrations/userInteraction.ts +++ b/src/js/tracing/integrations/userInteraction.ts @@ -45,7 +45,7 @@ export const startUserInteractionSpan = (userInteractionId: { logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing can not create transaction with undefined elementId.`); return undefined; } - if (!tracing.currentRoute) { + if (!tracing.state.currentRoute) { logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing can not create transaction without a current route.`); return undefined; } @@ -61,7 +61,7 @@ export const startUserInteractionSpan = (userInteractionId: { return undefined; } - const name = `${tracing.currentRoute}.${elementId}`; + const name = `${tracing.state.currentRoute}.${elementId}`; if ( activeTransaction && spanToJSON(activeTransaction).description === name && diff --git a/src/js/tracing/reactnativenavigation.ts b/src/js/tracing/reactnativenavigation.ts index 39d1dddf79..b07d6caff2 100644 --- a/src/js/tracing/reactnativenavigation.ts +++ b/src/js/tracing/reactnativenavigation.ts @@ -1,8 +1,14 @@ -import { addBreadcrumb, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; +import { + addBreadcrumb, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + spanToJSON, +} from '@sentry/core'; import type { Span } from '@sentry/types'; import type { EmitterSubscription } from '../utils/rnlibrariesinterface'; import { isSentrySpan } from '../utils/span'; +import { DEFAULT_NAVIGATION_SPAN_NAME } from './reactnativetracing'; import type { OnConfirmRoute, TransactionCreator } from './routingInstrumentation'; import { InternalRoutingInstrumentation } from './routingInstrumentation'; import type { BeforeNavigate } from './types'; @@ -124,7 +130,7 @@ export class ReactNativeNavigationInstrumentation extends InternalRoutingInstrum this._discardLatestTransaction(); } - this._latestTransaction = this.onRouteWillChange({ name: 'Route Change' }); + this._latestTransaction = this.onRouteWillChange({ name: DEFAULT_NAVIGATION_SPAN_NAME }); this._stateChangeTimeout = setTimeout( this._discardLatestTransaction.bind(this), @@ -151,7 +157,9 @@ export class ReactNativeNavigationInstrumentation extends InternalRoutingInstrum const routeHasBeenSeen = this._recentComponentIds.includes(event.componentId); - this._latestTransaction.updateName(event.componentName); + if (spanToJSON(this._latestTransaction).description === DEFAULT_NAVIGATION_SPAN_NAME) { + this._latestTransaction.updateName(event.componentName); + } this._latestTransaction.setAttributes({ // TODO: Should we include pass props? I don't know exactly what it contains, cant find it in the RNavigation docs 'route.name': event.componentName, diff --git a/src/js/tracing/reactnativetracing.ts b/src/js/tracing/reactnativetracing.ts index b6fec52952..d9e098d682 100644 --- a/src/js/tracing/reactnativetracing.ts +++ b/src/js/tracing/reactnativetracing.ts @@ -1,43 +1,51 @@ /* eslint-disable max-lines */ -import type { RequestInstrumentationOptions } from '@sentry/browser'; -import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from '@sentry/browser'; -import { getClient, SEMANTIC_ATTRIBUTE_SENTRY_OP, spanToJSON } from '@sentry/core'; -import type { Client, Event, Integration, Span } from '@sentry/types'; +import { instrumentOutgoingRequests } from '@sentry/browser'; +import { getClient, getCurrentScope } from '@sentry/core'; +import type { Client, Event, Integration, StartSpanOptions } from '@sentry/types'; import { logger } from '@sentry/utils'; -import type { RoutingInstrumentationInstance } from '../tracing/routingInstrumentation'; -import { startIdleNavigationSpan } from './span'; -import type { BeforeNavigate } from './types'; +import type { RoutingInstrumentationInstance } from './routingInstrumentation'; +import { addDefaultOpForSpanFrom, startIdleNavigationSpan } from './span'; -export interface ReactNativeTracingOptions extends RequestInstrumentationOptions { +export const INTEGRATION_NAME = 'ReactNativeTracing'; + +export interface ReactNativeTracingOptions { /** - * @deprecated Replaced by idleTimeoutMs + * The time that has to pass without any span being created. + * If this time is exceeded, the idle span will finish. + * + * @default 1_000 (ms) */ - idleTimeout: number; + idleTimeoutMs: number; /** - * @deprecated Replaced by maxTransactionDurationMs + * The max. time an idle span may run. + * If this time is exceeded, the idle span will finish no matter what. + * + * @default 60_0000 (ms) */ - maxTransactionDuration: number; + finalTimeoutMs: number; /** - * The time to wait in ms until the transaction will be finished. The transaction will use the end timestamp of - * the last finished span as the endtime for the transaction. - * Time is in ms. + * Flag to disable patching all together for fetch requests. * - * Default: 1000 + * @default true */ - idleTimeoutMs: number; + traceFetch: boolean; /** - * The maximum duration (transaction duration + idle timeout) of a transaction - * before it will be marked as "deadline_exceeded". - * If you never want to mark a transaction set it to 0. - * Time is in ms. + * Flag to disable patching all together for xhr requests. * - * Default: 600000 + * @default true */ - finalTimeoutMs: number; + traceXHR: boolean; + + /** + * If true, Sentry will capture http timings and add them to the corresponding http spans. + * + * @default true + */ + enableHTTPTimings: boolean; /** * The routing instrumentation to be used with the tracing integration. @@ -49,184 +57,132 @@ export interface ReactNativeTracingOptions extends RequestInstrumentationOptions * Does not sample transactions that are from routes that have been seen any more and don't have any spans. * This removes a lot of the clutter as most back navigation transactions are now ignored. * - * Default: true + * @default true */ ignoreEmptyBackNavigationTransactions: boolean; /** - * beforeNavigate is called before a navigation transaction is created and allows users to modify transaction - * context data, or drop the transaction entirely (by setting `sampled = false` in the context). - * - * @param context: The context data which will be passed to `startTransaction` by default + * A callback which is called before a span for a navigation is started. + * It receives the options passed to `startSpan`, and expects to return an updated options object. + */ + beforeStartSpan?: (options: StartSpanOptions) => StartSpanOptions; + + /** + * This function will be called before creating a span for a request with the given url. + * Return false if you don't want a span for the given url. * - * @returns A (potentially) modified context object, with `sampled = false` if the transaction should be dropped. + * @default (url: string) => true */ - beforeNavigate: BeforeNavigate; + shouldCreateSpanForRequest?(this: void, url: string): boolean; } const DEFAULT_TRACE_PROPAGATION_TARGETS = ['localhost', /^\/(?!\/)/]; +export const DEFAULT_NAVIGATION_SPAN_NAME = 'Route Change'; const defaultReactNativeTracingOptions: ReactNativeTracingOptions = { - ...defaultRequestInstrumentationOptions, - idleTimeout: 1000, - maxTransactionDuration: 600, - idleTimeoutMs: 1000, - finalTimeoutMs: 600000, + idleTimeoutMs: 1_000, + finalTimeoutMs: 60_0000, + traceFetch: true, + traceXHR: true, + enableHTTPTimings: true, ignoreEmptyBackNavigationTransactions: true, - beforeNavigate: context => context, }; -/** - * Tracing integration for React Native. - */ -export class ReactNativeTracing implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'ReactNativeTracing'; - - /** - * @inheritDoc - */ - public name: string = ReactNativeTracing.id; - - /** ReactNativeTracing options */ - public options: ReactNativeTracingOptions; - - public useAppStartWithProfiler: boolean = false; - - public currentRoute?: string; - - private _hasSetTracePropagationTargets: boolean; - private _currentViewName: string | undefined; - private _client: Client | undefined; - - public constructor(options: Partial = {}) { - this._hasSetTracePropagationTargets = !!( - options && - // eslint-disable-next-line deprecation/deprecation - options.tracePropagationTargets - ); - - this.options = { - ...defaultReactNativeTracingOptions, - ...options, - finalTimeoutMs: - options.finalTimeoutMs ?? - // eslint-disable-next-line deprecation/deprecation - (typeof options.maxTransactionDuration === 'number' - ? // eslint-disable-next-line deprecation/deprecation - options.maxTransactionDuration * 1000 - : undefined) ?? - defaultReactNativeTracingOptions.finalTimeoutMs, - idleTimeoutMs: - options.idleTimeoutMs ?? - // eslint-disable-next-line deprecation/deprecation - options.idleTimeout ?? - defaultReactNativeTracingOptions.idleTimeoutMs, - }; - } +type ReactNativeTracingState = { + currentRoute: string | undefined; +}; - /** - * Registers routing and request instrumentation. - */ - public setup(client: Client): void { - const clientOptions = client && client.getOptions(); - - // eslint-disable-next-line @typescript-eslint/unbound-method - const { - traceFetch, - traceXHR, - // eslint-disable-next-line deprecation/deprecation - shouldCreateSpanForRequest, - // eslint-disable-next-line deprecation/deprecation - tracePropagationTargets: thisOptionsTracePropagationTargets, - routingInstrumentation, - } = this.options; - - const clientOptionsTracePropagationTargets = clientOptions && clientOptions.tracePropagationTargets; - const tracePropagationTargets = - clientOptionsTracePropagationTargets || - (this._hasSetTracePropagationTargets && thisOptionsTracePropagationTargets) || - DEFAULT_TRACE_PROPAGATION_TARGETS; - - if (routingInstrumentation) { - routingInstrumentation.registerRoutingInstrumentation( - this._onRouteWillChange.bind(this), - this.options.beforeNavigate, - this._onConfirmRoute.bind(this), +export const reactNativeTracingIntegration = ( + options: Partial = {}, +): Integration & { + options: ReactNativeTracingOptions; + state: ReactNativeTracingState; +} => { + const state: ReactNativeTracingState = { + currentRoute: undefined, + }; + + const finalOptions = { + ...defaultReactNativeTracingOptions, + ...options, + beforeStartSpan: options.beforeStartSpan ?? ((options: StartSpanOptions) => options), + finalTimeoutMs: options.finalTimeoutMs ?? defaultReactNativeTracingOptions.finalTimeoutMs, + idleTimeoutMs: options.idleTimeoutMs ?? defaultReactNativeTracingOptions.idleTimeoutMs, + }; + + const setup = (client: Client): void => { + if (finalOptions.routingInstrumentation) { + const idleNavigationSpanOptions = { + finalTimeout: finalOptions.finalTimeoutMs, + idleTimeout: finalOptions.idleTimeoutMs, + ignoreEmptyBackNavigationTransactions: finalOptions.ignoreEmptyBackNavigationTransactions, + }; + finalOptions.routingInstrumentation.registerRoutingInstrumentation( + navigationInstrumentationOptions => + startIdleNavigationSpan( + finalOptions.beforeStartSpan({ + name: DEFAULT_NAVIGATION_SPAN_NAME, + op: 'navigation', + forceTransaction: true, + scope: getCurrentScope(), + ...navigationInstrumentationOptions, + }), + idleNavigationSpanOptions, + ), + () => { + // no-op, replaced by beforeStartSpan, will be removed in the future + }, + (currentViewName: string | undefined) => { + state.currentRoute = currentViewName; + }, ); } else { - logger.log('[ReactNativeTracing] Not instrumenting route changes as routingInstrumentation has not been set.'); + logger.log(`[${INTEGRATION_NAME}] Not instrumenting route changes as routingInstrumentation has not been set.`); } addDefaultOpForSpanFrom(client); instrumentOutgoingRequests({ - traceFetch, - traceXHR, - shouldCreateSpanForRequest, - tracePropagationTargets, + traceFetch: finalOptions.traceFetch, + traceXHR: finalOptions.traceXHR, + shouldCreateSpanForRequest: finalOptions.shouldCreateSpanForRequest, + tracePropagationTargets: client.getOptions().tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS, }); - } + }; - /** - * @inheritdoc - */ - public processEvent(event: Event): Promise | Event { - const eventWithView = this._getCurrentViewEventProcessor(event); - return eventWithView; - } - - /** - * Sets the current view name into the app context. - * @param event Le event. - */ - private _getCurrentViewEventProcessor(event: Event): Event { - if (event.contexts && this._currentViewName) { - event.contexts.app = { view_names: [this._currentViewName], ...event.contexts.app }; + const processEvent = (event: Event): Event => { + if (event.contexts && state.currentRoute) { + event.contexts.app = { view_names: [state.currentRoute], ...event.contexts.app }; } return event; - } + }; + + return { + name: INTEGRATION_NAME, + setup, + processEvent, + options: finalOptions, + state, + }; +}; - /** To be called when the route changes, but BEFORE the components of the new route mount. */ - private _onRouteWillChange(): Span | undefined { - return startIdleNavigationSpan( - { - name: 'Route Change', - }, - { - finalTimeout: this.options.finalTimeoutMs, - idleTimeout: this.options.idleTimeoutMs, - ignoreEmptyBackNavigationTransactions: this.options.ignoreEmptyBackNavigationTransactions, - }, - ); - } - - /** - * Save the current route to set it in context during event processing. - */ - private _onConfirmRoute(currentViewName: string | undefined): void { - this._currentViewName = currentViewName; - this.currentRoute = currentViewName; - } -} +export type ReactNativeTracingIntegration = ReturnType; /** * Returns the current React Native Tracing integration. */ -export function getCurrentReactNativeTracingIntegration(): ReactNativeTracing | undefined { +export function getCurrentReactNativeTracingIntegration(): ReactNativeTracingIntegration | undefined { const client = getClient(); if (!client) { return undefined; } - return client.getIntegrationByName(ReactNativeTracing.id) as ReactNativeTracing | undefined; + + return getReactNativeTracingIntegration(client); } -function addDefaultOpForSpanFrom(client: Client): void { - client.on('spanStart', (span: Span) => { - if (!spanToJSON(span).op) { - span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'default'); - } - }); +/** + * Returns React Native Tracing integration of given client. + */ +export function getReactNativeTracingIntegration(client: Client): ReactNativeTracingIntegration | undefined { + return client.getIntegrationByName(INTEGRATION_NAME) as ReactNativeTracingIntegration | undefined; } diff --git a/src/js/tracing/reactnavigation.ts b/src/js/tracing/reactnavigation.ts index 0b86b4e34d..965f7980d0 100644 --- a/src/js/tracing/reactnavigation.ts +++ b/src/js/tracing/reactnavigation.ts @@ -16,6 +16,7 @@ import { type SentryEventEmitter, createSentryEventEmitter, NewFrameEventName } import { isSentrySpan } from '../utils/span'; import { RN_GLOBAL_OBJ } from '../utils/worldwide'; import { NATIVE } from '../wrapper'; +import { DEFAULT_NAVIGATION_SPAN_NAME } from './reactnativetracing'; import type { OnConfirmRoute, TransactionCreator } from './routingInstrumentation'; import { InternalRoutingInstrumentation } from './routingInstrumentation'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from './semanticAttributes'; @@ -192,7 +193,7 @@ export class ReactNavigationInstrumentation extends InternalRoutingInstrumentati this._clearStateChangeTimeout(); } - this._latestTransaction = this.onRouteWillChange({ name: 'Route Change' }); + this._latestTransaction = this.onRouteWillChange({ name: DEFAULT_NAVIGATION_SPAN_NAME }); if (this._options.enableTimeToInitialDisplay) { this._navigationProcessingSpan = startInactiveSpan({ @@ -288,7 +289,9 @@ export class ReactNavigationInstrumentation extends InternalRoutingInstrumentati this._navigationProcessingSpan?.end(stateChangedTimestamp); this._navigationProcessingSpan = undefined; - this._latestTransaction.updateName(route.name); + if (spanToJSON(this._latestTransaction).description === DEFAULT_NAVIGATION_SPAN_NAME) { + this._latestTransaction.updateName(route.name); + } this._latestTransaction.setAttributes({ 'route.name': route.name, 'route.key': route.key, @@ -303,7 +306,6 @@ export class ReactNavigationInstrumentation extends InternalRoutingInstrumentati [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', }); - this._beforeNavigate?.(this._latestTransaction); // Clear the timeout so the transaction does not get cancelled. this._clearStateChangeTimeout(); diff --git a/src/js/tracing/span.ts b/src/js/tracing/span.ts index c6d7b8bc83..f73707ccf5 100644 --- a/src/js/tracing/span.ts +++ b/src/js/tracing/span.ts @@ -2,12 +2,13 @@ import { getActiveSpan, getClient, getCurrentScope, + SEMANTIC_ATTRIBUTE_SENTRY_OP, SentryNonRecordingSpan, SPAN_STATUS_ERROR, spanToJSON, startIdleSpan as coreStartIdleSpan, } from '@sentry/core'; -import type { Scope, Span, StartSpanOptions } from '@sentry/types'; +import type { Client, Scope, Span, StartSpanOptions } from '@sentry/types'; import { generatePropagationContext, logger } from '@sentry/utils'; import { isRootSpan } from '../utils/span'; @@ -15,13 +16,7 @@ import { adjustTransactionDuration, cancelInBackground, ignoreEmptyBackNavigatio import { SPAN_ORIGIN_AUTO_INTERACTION } from './origin'; export const startIdleNavigationSpan = ( - { - name, - op, - }: { - name?: string; - op?: string; - } = {}, + startSpanOption: StartSpanOptions, { finalTimeout, idleTimeout, @@ -47,15 +42,12 @@ export const startIdleNavigationSpan = ( activeSpan.end(); } - const expandedContext: StartSpanOptions = { - name, - op, - forceTransaction: true, - scope: getCurrentScope(), - }; - - const idleSpan = startIdleSpan(expandedContext, { finalTimeout, idleTimeout }); - logger.log(`[ReactNativeTracing] Starting ${op || 'unknown op'} transaction "${name}" on scope`); + const idleSpan = startIdleSpan(startSpanOption, { finalTimeout, idleTimeout }); + logger.log( + `[ReactNativeTracing] Starting ${startSpanOption.op || 'unknown op'} transaction "${ + startSpanOption.name + }" on scope`, + ); adjustTransactionDuration(client, idleSpan, finalTimeout); if (ignoreEmptyBackNavigationTransactions) { @@ -109,3 +101,14 @@ export function clearActiveSpanFromScope(scope: ScopeWithMaybeSpan): void { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete scope[SCOPE_SPAN_FIELD]; } + +/** + * Ensures that all created spans have an operation name. + */ +export function addDefaultOpForSpanFrom(client: Client): void { + client.on('spanStart', (span: Span) => { + if (!spanToJSON(span).op) { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'default'); + } + }); +} diff --git a/test/client.test.ts b/test/client.test.ts index fe0ea65a50..d4a057b316 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -9,7 +9,7 @@ import * as RN from 'react-native'; import { ReactNativeClient } from '../src/js/client'; import type { ReactNativeClientOptions } from '../src/js/options'; import type { RoutingInstrumentationInstance } from '../src/js/tracing'; -import { ReactNativeTracing } from '../src/js/tracing'; +import { reactNativeTracingIntegration } from '../src/js/tracing'; import { NativeTransport } from '../src/js/transports/native'; import { SDK_NAME, SDK_PACKAGE_NAME, SDK_VERSION } from '../src/js/version'; import { NATIVE } from '../src/js/wrapper'; @@ -621,7 +621,7 @@ describe('Tests ReactNativeClient', () => { mockedOptions({ dsn: EXAMPLE_DSN, integrations: [ - new ReactNativeTracing({ + reactNativeTracingIntegration({ routingInstrumentation: mockRoutingInstrumentation, }), ], diff --git a/test/sdk.test.ts b/test/sdk.test.ts index 522aa586ab..1c56a9d465 100644 --- a/test/sdk.test.ts +++ b/test/sdk.test.ts @@ -12,7 +12,12 @@ import type { BaseTransportOptions, ClientOptions, Integration, Scope } from '@s import { logger } from '@sentry/utils'; import { init, withScope } from '../src/js/sdk'; -import { ReactNativeTracing, ReactNavigationInstrumentation } from '../src/js/tracing'; +import type { ReactNativeTracingIntegration } from '../src/js/tracing'; +import { + REACT_NATIVE_TRACING_INTEGRATION_NAME, + reactNativeTracingIntegration, + ReactNavigationInstrumentation, +} from '../src/js/tracing'; import { makeNativeTransport } from '../src/js/transports/native'; import { getDefaultEnvironment, isExpoGo, notWeb } from '../src/js/utils/environment'; import { NATIVE } from './mockWrapper'; @@ -30,9 +35,9 @@ describe('Tests the SDK functionality', () => { describe('init', () => { describe('enableAutoPerformanceTracing', () => { - const reactNavigationInstrumentation = (): ReactNativeTracing => { + const reactNavigationInstrumentation = (): ReactNativeTracingIntegration => { const nav = new ReactNavigationInstrumentation(); - return new ReactNativeTracing({ routingInstrumentation: nav }); + return reactNativeTracingIntegration({ routingInstrumentation: nav }); }; it('Auto Performance is disabled by default', () => { @@ -84,7 +89,9 @@ describe('Tests the SDK functionality', () => { }); const options = usedIntegrations(); - expect(options.filter(integration => integration.name === ReactNativeTracing.id).length).toBe(1); + expect(options.filter(integration => integration.name === REACT_NATIVE_TRACING_INTEGRATION_NAME).length).toBe( + 1, + ); expect(options.some(integration => integration === tracing)).toBe(true); }); @@ -97,7 +104,9 @@ describe('Tests the SDK functionality', () => { }); const options = usedIntegrations(); - expect(options.filter(integration => integration.name === ReactNativeTracing.id).length).toBe(1); + expect(options.filter(integration => integration.name === REACT_NATIVE_TRACING_INTEGRATION_NAME).length).toBe( + 1, + ); expect(options.some(integration => integration === tracing)).toBe(true); }); }); @@ -681,5 +690,5 @@ function usedIntegrations(): Integration[] { } function autoPerformanceIsEnabled(): boolean { - return usedIntegrations().some(integration => integration.name === ReactNativeTracing.id); + return usedIntegrations().some(integration => integration.name === REACT_NATIVE_TRACING_INTEGRATION_NAME); } diff --git a/test/tracing/addTracingExtensions.test.ts b/test/tracing/addTracingExtensions.test.ts index bdc60b5578..4d4c5384c3 100644 --- a/test/tracing/addTracingExtensions.test.ts +++ b/test/tracing/addTracingExtensions.test.ts @@ -1,6 +1,6 @@ import { getCurrentScope, spanToJSON, startSpanManual } from '@sentry/core'; -import { ReactNativeTracing } from '../../src/js'; +import { reactNativeTracingIntegration } from '../../src/js'; import { type TestClient, setupTestClient } from '../mocks/client'; describe('Tracing extensions', () => { @@ -8,7 +8,7 @@ describe('Tracing extensions', () => { beforeEach(() => { client = setupTestClient({ - integrations: [new ReactNativeTracing()], + integrations: [reactNativeTracingIntegration()], }); }); diff --git a/test/tracing/gesturetracing.test.ts b/test/tracing/gesturetracing.test.ts index a0171613cf..d0086827b2 100644 --- a/test/tracing/gesturetracing.test.ts +++ b/test/tracing/gesturetracing.test.ts @@ -8,7 +8,8 @@ import { sentryTraceGesture, } from '../../src/js/tracing/gesturetracing'; import { startUserInteractionSpan } from '../../src/js/tracing/integrations/userInteraction'; -import { ReactNativeTracing } from '../../src/js/tracing/reactnativetracing'; +import type { ReactNativeTracingIntegration } from '../../src/js/tracing/reactnativetracing'; +import { reactNativeTracingIntegration } from '../../src/js/tracing/reactnativetracing'; import { type TestClient, setupTestClient } from '../mocks/client'; import type { MockedRoutingInstrumentation } from './mockedrountinginstrumention'; import { createMockedRoutingInstrumentation } from './mockedrountinginstrumention'; @@ -37,7 +38,7 @@ describe('GestureTracing', () => { describe('gracefully fails on invalid gestures', () => { it('gesture is undefined', () => { - const gesture = undefined; + const gesture: unknown = undefined; expect(sentryTraceGesture(label, gesture)).toBeUndefined(); }); @@ -49,7 +50,7 @@ describe('GestureTracing', () => { describe('traces gestures', () => { let client: TestClient; - let tracing: ReactNativeTracing; + let tracing: ReactNativeTracingIntegration; let mockedRoutingInstrumentation: MockedRoutingInstrumentation; let mockedGesture: MockGesture; @@ -60,7 +61,7 @@ describe('GestureTracing', () => { enableUserInteractionTracing: true, }); mockedRoutingInstrumentation = createMockedRoutingInstrumentation(); - tracing = new ReactNativeTracing({ + tracing = reactNativeTracingIntegration({ routingInstrumentation: mockedRoutingInstrumentation, }); client.addIntegration(tracing); diff --git a/test/tracing/integrations/userInteraction.test.ts b/test/tracing/integrations/userInteraction.test.ts index dfd1b44a4d..01bcd86a0c 100644 --- a/test/tracing/integrations/userInteraction.test.ts +++ b/test/tracing/integrations/userInteraction.test.ts @@ -13,7 +13,8 @@ import { startUserInteractionSpan, userInteractionIntegration, } from '../../../src/js/tracing/integrations/userInteraction'; -import { ReactNativeTracing } from '../../../src/js/tracing/reactnativetracing'; +import type { ReactNativeTracingIntegration } from '../../../src/js/tracing/reactnativetracing'; +import { reactNativeTracingIntegration } from '../../../src/js/tracing/reactnativetracing'; import { NATIVE } from '../../../src/js/wrapper'; import type { TestClient } from '../../mocks/client'; import { setupTestClient } from '../../mocks/client'; @@ -57,7 +58,7 @@ jest.mock('../../../src/js/wrapper', () => { describe('User Interaction Tracing', () => { let client: TestClient; - let tracing: ReactNativeTracing; + let tracing: ReactNativeTracingIntegration; let mockedUserInteractionId: { elementId: string | undefined; op: string }; let mockedRoutingInstrumentation: MockedRoutingInstrumentation; @@ -98,7 +99,7 @@ describe('User Interaction Tracing', () => { describe('enabled user interaction', () => { beforeEach(() => { - tracing = new ReactNativeTracing({ + tracing = reactNativeTracingIntegration({ routingInstrumentation: mockedRoutingInstrumentation, }); client.addIntegration(userInteractionIntegration()); diff --git a/test/tracing/reactnativenavigation.test.ts b/test/tracing/reactnativenavigation.test.ts index 44c3a1abae..f07c7c5505 100644 --- a/test/tracing/reactnativenavigation.test.ts +++ b/test/tracing/reactnativenavigation.test.ts @@ -7,10 +7,10 @@ import { setCurrentClient, spanToJSON, } from '@sentry/core'; -import type { Event } from '@sentry/types'; +import type { Event, StartSpanOptions } from '@sentry/types'; import type { EmitterSubscription } from 'react-native'; -import { ReactNativeTracing } from '../../src/js'; +import { reactNativeTracingIntegration } from '../../src/js'; import type { BottomTabPressedEvent, ComponentWillAppearEvent, @@ -31,7 +31,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, } from '../../src/js/tracing/semanticAttributes'; -import type { BeforeNavigate } from '../../src/js/tracing/types'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; interface MockEventsRegistry extends EventsRegistry { @@ -94,10 +93,11 @@ describe('React Native Navigation Instrumentation', () => { ); }); - test('Transaction context is changed with beforeNavigate', async () => { + test('start span options are changes by before start span callback', async () => { setupTestClient({ - beforeNavigate: span => { - span.updateName('New Name'); + beforeStartSpan: startSpanOptions => { + startSpanOptions.name = 'New Name'; + return startSpanOptions; }, }); @@ -351,7 +351,7 @@ describe('React Native Navigation Instrumentation', () => { function setupTestClient( setupOptions: { - beforeNavigate?: BeforeNavigate; + beforeStartSpan?: (options: StartSpanOptions) => StartSpanOptions; enableTabsInstrumentation?: boolean; } = {}, ) { @@ -368,15 +368,15 @@ describe('React Native Navigation Instrumentation', () => { }, ); - const rnTracing = new ReactNativeTracing({ + const rnTracing = reactNativeTracingIntegration({ routingInstrumentation: rNavigation, - enableStallTracking: false, - enableNativeFramesTracking: false, - beforeNavigate: setupOptions.beforeNavigate, + beforeStartSpan: setupOptions.beforeStartSpan, }); const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0, + enableStallTracking: false, + enableNativeFramesTracking: false, integrations: [rnTracing], enableAppStartTracking: false, }); diff --git a/test/tracing/reactnativetracing.test.ts b/test/tracing/reactnativetracing.test.ts index b0d584ca48..916489b1cc 100644 --- a/test/tracing/reactnativetracing.test.ts +++ b/test/tracing/reactnativetracing.test.ts @@ -59,7 +59,7 @@ jest.mock('react-native/Libraries/AppState/AppState', () => mockedAppState); import { getActiveSpan, spanToJSON } from '@sentry/browser'; import type { AppState, AppStateStatus } from 'react-native'; -import { ReactNativeTracing } from '../../src/js/tracing/reactnativetracing'; +import { reactNativeTracingIntegration } from '../../src/js/tracing/reactnativetracing'; import { NATIVE } from '../../src/js/wrapper'; import type { TestClient } from '../mocks/client'; import { setupTestClient } from '../mocks/client'; @@ -84,30 +84,12 @@ describe('ReactNativeTracing', () => { }); describe('trace propagation targets', () => { - it('uses tracePropagationTargets', () => { - const instrumentOutgoingRequests = jest.spyOn(SentryBrowser, 'instrumentOutgoingRequests'); - setupTestClient({ - enableStallTracking: false, - integrations: [ - new ReactNativeTracing({ - tracePropagationTargets: ['test1', 'test2'], - }), - ], - }); - - expect(instrumentOutgoingRequests).toBeCalledWith( - expect.objectContaining({ - tracePropagationTargets: ['test1', 'test2'], - }), - ); - }); - it('uses tracePropagationTargets from client options', () => { const instrumentOutgoingRequests = jest.spyOn(SentryBrowser, 'instrumentOutgoingRequests'); setupTestClient({ tracePropagationTargets: ['test1', 'test2'], enableStallTracking: false, - integrations: [new ReactNativeTracing({})], + integrations: [reactNativeTracingIntegration()], }); expect(instrumentOutgoingRequests).toBeCalledWith( @@ -121,7 +103,7 @@ describe('ReactNativeTracing', () => { const instrumentOutgoingRequests = jest.spyOn(SentryBrowser, 'instrumentOutgoingRequests'); setupTestClient({ enableStallTracking: false, - integrations: [new ReactNativeTracing({})], + integrations: [reactNativeTracingIntegration()], }); expect(instrumentOutgoingRequests).toBeCalledWith( @@ -130,25 +112,6 @@ describe('ReactNativeTracing', () => { }), ); }); - - it('client tracePropagationTargets takes priority over integration options', () => { - const instrumentOutgoingRequests = jest.spyOn(SentryBrowser, 'instrumentOutgoingRequests'); - setupTestClient({ - tracePropagationTargets: ['test1', 'test2'], - enableStallTracking: false, - integrations: [ - new ReactNativeTracing({ - tracePropagationTargets: ['test3', 'test4'], - }), - ], - }); - - expect(instrumentOutgoingRequests).toBeCalledWith( - expect.objectContaining({ - tracePropagationTargets: ['test1', 'test2'], - }), - ); - }); }); describe('Tracing Instrumentation', () => { @@ -161,7 +124,7 @@ describe('ReactNativeTracing', () => { describe('With routing instrumentation', () => { it('Cancels route transaction when app goes to background', async () => { const routingInstrumentation = new RoutingInstrumentation(); - const integration = new ReactNativeTracing({ + const integration = reactNativeTracingIntegration({ routingInstrumentation, }); @@ -191,7 +154,7 @@ describe('ReactNativeTracing', () => { const routingInstrumentation = new RoutingInstrumentation(); setupTestClient({ integrations: [ - new ReactNativeTracing({ + reactNativeTracingIntegration({ routingInstrumentation, }), ], @@ -221,7 +184,7 @@ describe('ReactNativeTracing', () => { describe('_onConfirmRoute', () => { it('Sets app context', async () => { const routing = new RoutingInstrumentation(); - const integration = new ReactNativeTracing({ + const integration = reactNativeTracingIntegration({ routingInstrumentation: routing, }); @@ -243,7 +206,7 @@ describe('ReactNativeTracing', () => { describe('View Names event processor', () => { it('Do not overwrite event app context', () => { const routing = new RoutingInstrumentation(); - const integration = new ReactNativeTracing({ + const integration = reactNativeTracingIntegration({ routingInstrumentation: routing, }); @@ -251,16 +214,15 @@ describe('ReactNativeTracing', () => { const event: Event = { contexts: { app: { appKey: 'value' } } }; const expectedEvent: Event = { contexts: { app: { appKey: 'value', view_names: [expectedRouteName] } } }; - // @ts-expect-error only for testing. - integration._currentViewName = expectedRouteName; - const processedEvent = integration['_getCurrentViewEventProcessor'](event); + integration.state.currentRoute = expectedRouteName; + const processedEvent = integration.processEvent(event, {}, client); expect(processedEvent).toEqual(expectedEvent); }); it('Do not add view_names if context is undefined', () => { const routing = new RoutingInstrumentation(); - const integration = new ReactNativeTracing({ + const integration = reactNativeTracingIntegration({ routingInstrumentation: routing, }); @@ -268,23 +230,22 @@ describe('ReactNativeTracing', () => { const event: Event = { release: 'value' }; const expectedEvent: Event = { release: 'value' }; - // @ts-expect-error only for testing. - integration._currentViewName = expectedRouteName; - const processedEvent = integration['_getCurrentViewEventProcessor'](event); + integration.state.currentRoute = expectedRouteName; + const processedEvent = integration.processEvent(event, {}, client); expect(processedEvent).toEqual(expectedEvent); }); it('ignore view_names if undefined', () => { const routing = new RoutingInstrumentation(); - const integration = new ReactNativeTracing({ + const integration = reactNativeTracingIntegration({ routingInstrumentation: routing, }); const event: Event = { contexts: { app: { key: 'value ' } } }; const expectedEvent: Event = { contexts: { app: { key: 'value ' } } }; - const processedEvent = integration['_getCurrentViewEventProcessor'](event); + const processedEvent = integration.processEvent(event, {}, client); expect(processedEvent).toEqual(expectedEvent); }); diff --git a/test/tracing/reactnavigation.stalltracking.test.ts b/test/tracing/reactnavigation.stalltracking.test.ts index 2c01838016..b3548a98ab 100644 --- a/test/tracing/reactnavigation.stalltracking.test.ts +++ b/test/tracing/reactnavigation.stalltracking.test.ts @@ -5,7 +5,7 @@ jest.mock('../../src/js/tracing/utils', () => ({ import { getCurrentScope, getGlobalScope, getIsolationScope, setCurrentClient, startSpanManual } from '@sentry/core'; -import { ReactNativeTracing, ReactNavigationInstrumentation } from '../../src/js'; +import { reactNativeTracingIntegration, ReactNavigationInstrumentation } from '../../src/js'; import { stallTrackingIntegration } from '../../src/js/tracing/integrations/stalltracking'; import { isNearToNow } from '../../src/js/tracing/utils'; import { RN_GLOBAL_OBJ } from '../../src/js/utils/worldwide'; @@ -29,7 +29,7 @@ describe('StallTracking with ReactNavigation', () => { const rnavigation = new ReactNavigationInstrumentation(); mockNavigation = createMockNavigationAndAttachTo(rnavigation); - const rnTracing = new ReactNativeTracing({ + const rnTracing = reactNativeTracingIntegration({ routingInstrumentation: rnavigation, }); diff --git a/test/tracing/reactnavigation.test.ts b/test/tracing/reactnavigation.test.ts index 1ba51d5d3f..55f76b40ac 100644 --- a/test/tracing/reactnavigation.test.ts +++ b/test/tracing/reactnavigation.test.ts @@ -1,8 +1,10 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { getCurrentScope, getGlobalScope, getIsolationScope, SentrySpan, setCurrentClient } from '@sentry/core'; +import type { StartSpanOptions } from '@sentry/types'; -import { ReactNativeTracing } from '../../src/js'; +import { reactNativeTracingIntegration } from '../../src/js'; +import { DEFAULT_NAVIGATION_SPAN_NAME } from '../../src/js/tracing/reactnativetracing'; import type { NavigationRoute } from '../../src/js/tracing/reactnavigation'; import { ReactNavigationInstrumentation } from '../../src/js/tracing/reactnavigation'; import { @@ -17,7 +19,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, } from '../../src/js/tracing/semanticAttributes'; -import type { BeforeNavigate } from '../../src/js/tracing/types'; import { RN_GLOBAL_OBJ } from '../../src/js/utils/worldwide'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; import { createMockNavigationAndAttachTo } from './reactnavigationutils'; @@ -152,10 +153,11 @@ describe('ReactNavigationInstrumentation', () => { ); }); - test('transaction context changed with beforeNavigate', async () => { + test('start span option changed in before start span callback', async () => { setupTestClient({ - beforeNavigate: span => { - span.updateName('New Span Name'); + beforeSpanStart: startSpanOption => { + startSpanOption.name = 'New Span Name'; + return startSpanOption; }, }); jest.runOnlyPendingTimers(); // Flush the init transaction @@ -295,7 +297,7 @@ describe('ReactNavigationInstrumentation', () => { routeChangeTimeoutMs: 200, }); - const mockTransaction = new SentrySpan({ sampled: true }); + const mockTransaction = new SentrySpan({ sampled: true, name: DEFAULT_NAVIGATION_SPAN_NAME }); const tracingListener = jest.fn(() => mockTransaction); instrumentation.registerRoutingInstrumentation( tracingListener as any, @@ -323,7 +325,7 @@ describe('ReactNavigationInstrumentation', () => { function setupTestClient( setupOptions: { - beforeNavigate?: BeforeNavigate; + beforeSpanStart?: (options: StartSpanOptions) => StartSpanOptions; } = {}, ) { const rNavigation = new ReactNavigationInstrumentation({ @@ -331,14 +333,14 @@ describe('ReactNavigationInstrumentation', () => { }); mockNavigation = createMockNavigationAndAttachTo(rNavigation); - const rnTracing = new ReactNativeTracing({ + const rnTracing = reactNativeTracingIntegration({ routingInstrumentation: rNavigation, - enableStallTracking: false, - enableNativeFramesTracking: false, - beforeNavigate: setupOptions.beforeNavigate, + beforeStartSpan: setupOptions.beforeSpanStart, }); const options = getDefaultTestClientOptions({ + enableNativeFramesTracking: false, + enableStallTracking: false, tracesSampleRate: 1.0, integrations: [rnTracing], enableAppStartTracking: false, diff --git a/test/tracing/reactnavigation.ttid.test.tsx b/test/tracing/reactnavigation.ttid.test.tsx index f82996c6e0..e6743fc7d5 100644 --- a/test/tracing/reactnavigation.ttid.test.tsx +++ b/test/tracing/reactnavigation.ttid.test.tsx @@ -531,10 +531,10 @@ function initSentry(sut: ReactNavigationInstrumentation): { const options: Sentry.ReactNativeOptions = { dsn: MOCK_DSN, enableTracing: true, + enableStallTracking: false, integrations: [ - new Sentry.ReactNativeTracing({ + Sentry.reactNativeTracingIntegration({ routingInstrumentation: sut, - enableStallTracking: false, ignoreEmptyBackNavigationTransactions: true, // default true }), ],