diff --git a/CHANGELOG.md b/CHANGELOG.md index 87274d1c1a..d909cf1d6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Features - Add experimental `enableStandaloneAppStartTracing` to send app start as a standalone `app.start` transaction ([#6359](https://github.com/getsentry/sentry-react-native/pull/6359)) +- Set `app.vitals.start.screen` on the standalone `app.start` transaction from the current route ([#6369](https://github.com/getsentry/sentry-react-native/pull/6369)) - Add `enableTurboModuleTracking` opt-in experimental option to enable Turbo Module performance tracking in the New Architecture ([#6307](https://github.com/getsentry/sentry-react-native/pull/6307)) - Use the runtime's native `btoa` for envelope base64 encoding when available, to improve `captureEnvelope` performance. Falls back to the bundled JS encoder if `btoa` is missing ([#6351](https://github.com/getsentry/sentry-react-native/pull/6351)). - Opt-in build-time auto-wrap for Expo Router's per-route `ErrorBoundary` ([#6347](https://github.com/getsentry/sentry-react-native/pull/6347)) diff --git a/packages/core/src/js/tracing/integrations/appStart.ts b/packages/core/src/js/tracing/integrations/appStart.ts index c628fa279e..79c2c0f79b 100644 --- a/packages/core/src/js/tracing/integrations/appStart.ts +++ b/packages/core/src/js/tracing/integrations/appStart.ts @@ -31,7 +31,9 @@ import { UI_LOAD as UI_LOAD_OP, } from '../ops'; import { SPAN_ORIGIN_AUTO_APP_START, SPAN_ORIGIN_MANUAL_APP_START } from '../origin'; +import { getCurrentReactNativeTracingIntegration } from '../reactnativetracing'; import { + SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN, SEMANTIC_ATTRIBUTE_APP_VITALS_START_TYPE, SEMANTIC_ATTRIBUTE_APP_VITALS_START_VALUE, SEMANTIC_ATTRIBUTE_SENTRY_OP, @@ -659,6 +661,15 @@ export const appStartIntegration = ({ event.contexts.trace.data[SEMANTIC_ATTRIBUTE_APP_VITALS_START_VALUE] = appStartDurationMs; event.contexts.trace.data[SEMANTIC_ATTRIBUTE_APP_VITALS_START_TYPE] = appStart.type; + // Screen shown when app start completes. Unlike the non-standalone `ui.load` transaction + // (whose name is the screen, which Relay backfills from), the standalone transaction is named + // `App Start`, so we set the screen explicitly. Sourced from the current route tracked by the + // tracing integration; omitted when no route has been registered yet at capture time. + const screen = getCurrentReactNativeTracingIntegration()?.state.currentRoute; + if (screen) { + event.contexts.trace.data[SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN] = screen; + } + // Minimal parent referencing the root transaction span, so the breakdown spans attach // directly under it (the helpers only read op/origin/span_id/trace_id/start_timestamp). // `data` is shared with the root trace context so frame data lands on the root span. diff --git a/packages/core/src/js/tracing/semanticAttributes.ts b/packages/core/src/js/tracing/semanticAttributes.ts index ab8f033000..1e7d3b4474 100644 --- a/packages/core/src/js/tracing/semanticAttributes.ts +++ b/packages/core/src/js/tracing/semanticAttributes.ts @@ -22,3 +22,4 @@ export const SEMANTIC_ATTRIBUTE_NAVIGATION_ACTION_TYPE = 'navigation.action_type // App start vitals (Span V2 / EAP). Emitted on the standalone `app.start` transaction. export const SEMANTIC_ATTRIBUTE_APP_VITALS_START_VALUE = 'app.vitals.start.value'; export const SEMANTIC_ATTRIBUTE_APP_VITALS_START_TYPE = 'app.vitals.start.type'; +export const SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN = 'app.vitals.start.screen'; diff --git a/packages/core/test/tracing/integrations/appStart.test.ts b/packages/core/test/tracing/integrations/appStart.test.ts index 9baf5be660..9f3f7661ef 100644 --- a/packages/core/test/tracing/integrations/appStart.test.ts +++ b/packages/core/test/tracing/integrations/appStart.test.ts @@ -36,7 +36,9 @@ import { setRootComponentCreationTimestampMs, } from '../../../src/js/tracing/integrations/appStart'; import { SPAN_ORIGIN_AUTO_APP_START, SPAN_ORIGIN_MANUAL_APP_START } from '../../../src/js/tracing/origin'; +import * as ReactNativeTracing from '../../../src/js/tracing/reactnativetracing'; import { + SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN, SEMANTIC_ATTRIBUTE_APP_VITALS_START_TYPE, SEMANTIC_ATTRIBUTE_APP_VITALS_START_VALUE, } from '../../../src/js/tracing/semanticAttributes'; @@ -147,6 +149,32 @@ describe('App Start Integration', () => { }); }); + it('sets app.vitals.start.screen from the current route', async () => { + const [timeOriginMilliseconds, appStartTimeMilliseconds] = mockAppStart({ cold: true }); + const screenSpy = jest + .spyOn(ReactNativeTracing, 'getCurrentReactNativeTracingIntegration') + .mockReturnValue({ state: { currentRoute: 'HomeScreen' } } as ReturnType< + typeof ReactNativeTracing.getCurrentReactNativeTracingIntegration + >); + + try { + const actualEvent = await captureStandAloneAppStart(); + expect(actualEvent).toEqual( + expectEventWithStandaloneColdAppStart(actualEvent, { timeOriginMilliseconds, appStartTimeMilliseconds }), + ); + expect(actualEvent?.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN]).toBe('HomeScreen'); + } finally { + screenSpy.mockRestore(); + } + }); + + it('omits app.vitals.start.screen when no route has been registered', async () => { + mockAppStart({ cold: true }); + + const actualEvent = await captureStandAloneAppStart(); + expect(actualEvent?.contexts?.trace?.data).not.toHaveProperty(SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN); + }); + it('Does not add any spans or measurements when App Start Span is longer than threshold', async () => { set__DEV__(false); mockTooLongAppStart();