diff --git a/CHANGELOG.md b/CHANGELOG.md index dd88be7f3c..ba913bda4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ ### Fixes +- App start data not attached to sampled transactions when preceded by unsampled transactions ([#5756](https://github.com/getsentry/sentry-react-native/pull/5756)) - Resolve relative `SOURCEMAP_FILE` paths against the project root in the Xcode build script ([#5730](https://github.com/getsentry/sentry-react-native/pull/5730)) - Fixes the issue with unit mismatch in `adjustTransactionDuration` ([#5740](https://github.com/getsentry/sentry-react-native/pull/5740)) - Handle `inactive` state for spans ([#5742](https://github.com/getsentry/sentry-react-native/pull/5742)) diff --git a/packages/core/src/js/tracing/integrations/appStart.ts b/packages/core/src/js/tracing/integrations/appStart.ts index 88f1d1d38f..09b2263ceb 100644 --- a/packages/core/src/js/tracing/integrations/appStart.ts +++ b/packages/core/src/js/tracing/integrations/appStart.ts @@ -7,6 +7,7 @@ import { getCurrentScope, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SentryNonRecordingSpan, + spanIsSampled, startInactiveSpan, timestampInSeconds, } from '@sentry/core'; @@ -241,6 +242,10 @@ export const appStartIntegration = ({ return; } + if (!spanIsSampled(rootSpan)) { + return; + } + setFirstStartedActiveRootSpanId(rootSpan.spanContext().spanId); }; diff --git a/packages/core/test/tracing/integrations/appStart.test.ts b/packages/core/test/tracing/integrations/appStart.test.ts index b5a26bf5f0..a15f931ba8 100644 --- a/packages/core/test/tracing/integrations/appStart.test.ts +++ b/packages/core/test/tracing/integrations/appStart.test.ts @@ -5,6 +5,7 @@ import { getIsolationScope, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SentryNonRecordingSpan, setCurrentClient, startInactiveSpan, timestampInSeconds, @@ -463,6 +464,50 @@ describe('App Start Integration', () => { }); }); + it('Does not lock firstStartedActiveRootSpanId to unsampled root span', async () => { + mockAppStart({ cold: true }); + + const integration = appStartIntegration(); + const client = new TestClient({ + ...getDefaultTestClientOptions(), + enableAppStartTracking: true, + tracesSampleRate: 1.0, + }); + setCurrentClient(client); + integration.setup(client); + integration.afterAllSetup(client); + + // Simulate an unsampled root span starting first + const unsampledSpan = new SentryNonRecordingSpan(); + client.emit('spanStart', unsampledSpan); + + // Then a sampled root span starts + const sampledSpan = startInactiveSpan({ + name: 'Sampled Root Span', + forceTransaction: true, + }); + const sampledSpanId = sampledSpan.spanContext().spanId; + + // Process a transaction event matching the sampled span + const event = getMinimalTransactionEvent(); + event.contexts!.trace!.span_id = sampledSpanId; + + const actualEvent = await processEventWithIntegration(integration, event); + + // App start should be attached to the sampled transaction + const appStartSpan = (actualEvent as TransactionEvent)?.spans?.find( + ({ description }) => description === 'Cold Start', + ); + expect(appStartSpan).toBeDefined(); + expect(appStartSpan).toEqual( + expect.objectContaining({ + description: 'Cold Start', + op: APP_START_COLD_OP, + }), + ); + expect((actualEvent as TransactionEvent)?.measurements?.[APP_START_COLD_MEASUREMENT]).toBeDefined(); + }); + it('Adds Cold App Start Span to Active Span', async () => { const [timeOriginMilliseconds, appStartTimeMilliseconds] = mockAppStart({ cold: true });