feat(span-first): Port native app start integration to V2 span API#3534
feat(span-first): Port native app start integration to V2 span API#3534buenaflor merged 43 commits intofeat/span-firstfrom
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace inline TTID/TTFD streaming code in SentryNavigatorObserver with TimeToDisplayTrackerV2.trackRoute(). Remove static ttfdSpan field and route SentryDisplay/SentryFlutter.currentDisplay() through the tracker on SentryFlutterOptions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…aming integration tests Make timeToDisplayTrackerV2 a non-nullable late field on SentryFlutterOptions instead of being created and set by SentryNavigatorObserver. Add didPop handling for streaming mode and integration tests covering the full span v2 TTID/TTFD lifecycle through the navigator observer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add V2 streaming span support to the native and generic app start integrations, gated by SentryTraceLifecycle.streaming. Key changes: - Add startTimestamp param to V2 span creation chain (Hub, Recording/ IdleRecordingSentrySpanV2) for backdated app start spans - Extract shared AppStartInfo/TimeSpan/parseNativeAppStart into native_app_start_data.dart for reuse across V1 and V2 handlers - Extend TimeToDisplayTrackerV2.trackRoute() to return SentrySpanV2 and accept optional startTimestamp/ttidEndTimestamp params - Create NativeAppStartHandlerV2 that uses trackRoute() with backdated timestamps and creates app start + phase + native child spans - Branch NativeAppStartIntegration and GenericAppStartIntegration on traceLifecycle to delegate to V1 or V2 handlers Co-Authored-By: Claude <noreply@anthropic.com>
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
|
Create root idle span synchronously during integration init via prepareRouteSpan() so user spans in initState can parent to it. When native app start data arrives, trackRoute() reuses the prepared span and backdates timestamps instead of creating a new one. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…to feat/span/native-app-start-v2
Replace outdated `trackRoute()` calls with the current API methods: `trackRootNavigation`, `trackNonRootNavigation`, and `prepareRootNavigation`. Add new test groups for `prepareRootNavigation` and the prepare-then-track interaction flow. Co-Authored-By: Claude <noreply@anthropic.com>
Remove debug print statement from NativeAppStartIntegration, delete obsolete FakeTimeToDisplayTrackerV2, update example app to use structured app start spans, and apply formatting fixes. Co-Authored-By: Claude <noreply@anthropic.com>
| /// | ||
| /// Returns `null` if validation fails (e.g. >60s duration, missing setup time). | ||
| @internal | ||
| AppStartInfo? parseNativeAppStart( |
There was a problem hiding this comment.
pull out the common things we can to share logic with native app start handler v2 and v1
There was a problem hiding this comment.
Pull request overview
Ports native + generic app-start instrumentation and time-to-display navigation spans to the V2 “streaming” span API, gated by SentryTraceLifecycle.streaming, to support span-first auto-instrumentation.
Changes:
- Adds root-navigation preparation/tracking APIs to
TimeToDisplayTrackerV2and updates navigation observer to usetrackNonRootNavigation. - Introduces
NativeAppStartHandlerV2(streaming) and factors native app-start parsing into sharednative_app_start_data.dart. - Extends Hub V2 span creation APIs to support
startTimestamp, with updated mocks and tests.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/flutter/lib/src/navigation/time_to_display_tracker_v2.dart | Adds root navigation preparation + tracking and a dedicated non-root tracking API for streaming spans. |
| packages/flutter/lib/src/navigation/sentry_navigator_observer.dart | Switches streaming navigation tracking to trackNonRootNavigation. |
| packages/flutter/lib/src/integrations/native_app_start_integration.dart | Routes native app-start handling to V2 handler when traceLifecycle == streaming and prepares root navigation early. |
| packages/flutter/lib/src/integrations/native_app_start_handler_v2.dart | New streaming handler creating app-start phase spans as V2 spans under a prepared root span. |
| packages/flutter/lib/src/integrations/native_app_start_handler.dart | Reuses shared native app-start parsing via parseNativeAppStart. |
| packages/flutter/lib/src/integrations/native_app_start_data.dart | New shared parsing/validation types for native app-start timing. |
| packages/flutter/lib/src/integrations/generic_app_start_integration.dart | Uses trackRootNavigation() in streaming mode instead of creating a V1 transaction. |
| packages/flutter/lib/src/sentry_flutter.dart | Wires NativeAppStartHandlerV2 into SDK init integration setup. |
| packages/flutter/test/navigation/time_to_display_tracker_v2_test.dart | Updates/extends tracker tests for root vs non-root navigation and preparation behavior. |
| packages/flutter/test/navigation/sentry_navigator_observer_test.dart | Updates streaming observer tests to expect non-root navigation tracking calls. |
| packages/flutter/test/navigation/fake_time_to_display_tracker_v2.dart | Updates fake tracker to match new API/return type. |
| packages/flutter/test/integrations/native_app_start_integration_test.dart | Updates integration fixture to provide a V2 native app-start handler. |
| packages/flutter/test/integrations/native_app_start_handler_v2_test.dart | New unit tests covering V2 native app-start span creation/backdating/origins. |
| packages/flutter/test/integrations/generic_app_start_integration_test.dart | Adds streaming-mode coverage for generic app start using V2 spans. |
| packages/flutter/test/mocks.mocks.dart | Regenerated mocks to include new startTimestamp params/defaults. |
| packages/flutter/example/lib/main.dart | Updates example to demonstrate custom span work during app start then report fully displayed. |
| packages/dart/lib/src/hub.dart | Adds startTimestamp support to startInactiveSpan/startIdleSpan and passes it into span constructors. |
| packages/dart/lib/src/hub_adapter.dart | Threads startTimestamp through adapter methods. |
| packages/dart/lib/src/noop_hub.dart | Updates NoOpHub signatures to accept startTimestamp. |
| packages/dart/lib/src/telemetry/span/recording_sentry_span_v2.dart | Adds startTimestamp support in constructors and an internal setter for backdating. |
| packages/dart/lib/src/telemetry/span/idle_recording_sentry_span_v2.dart | Threads startTimestamp into idle span construction. |
| packages/dart/test/hub_span_test.dart | Adds test coverage for startTimestamp behavior on inactive + idle spans. |
Comments suppressed due to low confidence (1)
packages/flutter/lib/src/navigation/time_to_display_tracker_v2.dart:183
- The new docstring says this "cancels all spans for the current route", but the implementation cancels any active idle span on the hub (including idle spans not created by this tracker, e.g. user-interaction idle spans). Consider tightening the wording or scoping the cancellation to only spans created/prepared by this tracker.
/// Cancels all spans for the current route and resets tracker state.
void cancelCurrentRoute() {
_ttfdSpan = null;
_preparedRootNavigationSpan = null;
final activeSpan = _hub.getActiveSpan();
if (activeSpan is IdleRecordingSentrySpanV2) {
activeSpan
..status = SentrySpanStatusV2.cancelled
..end();
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/flutter/lib/src/integrations/native_app_start_handler_v2.dart
Outdated
Show resolved
Hide resolved
packages/flutter/test/integrations/generic_app_start_integration_test.dart
Outdated
Show resolved
Hide resolved
- Remove unused `options` parameter from `parseNativeAppStart` - Fix index mismatch bug in native span ending by ending spans inline - Update test name and comment to reference `trackRootNavigation` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@cursor review |
|
@sentry review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
packages/flutter/lib/src/integrations/native_app_start_integration.dart
Outdated
Show resolved
Hide resolved
packages/dart/lib/src/telemetry/span/recording_sentry_span_v2.dart
Outdated
Show resolved
Hide resolved
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ve switch - Cancel orphaned idle span when handler returns early (null native start or >60s) - Use switch on SentryTraceLifecycle for exhaustive matching instead of if/else Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aligns constructor behavior with the setter which already calls .toUtc(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update hub span timestamp assertions to expect UTC normalization for explicit startTimestamp values on inactive and idle spans. Co-authored-by: Cursor <cursoragent@cursor.com>
Align native app start V2 test assertions with UTC normalization so span start times are compared consistently across environments. Co-authored-by: Cursor <cursoragent@cursor.com>
|
|
||
| appStartSpan.end(endTimestamp: appStartEnd); | ||
|
|
||
| // TODO(next-pr): add mobile vitals specific attributes later |
There was a problem hiding this comment.
spec is still tbd
| /// [SentryFlutter.currentDisplay] before [trackRootNavigation] fires. | ||
| /// Timestamps are backdated later in [trackRootNavigation]. | ||
| void prepareRootNavigation() { | ||
| assert(_preparedRootNavigationSpan == null, |
There was a problem hiding this comment.
realistically won't happen but let's assert so it shows up in debug at least as an error
Removed outdated comments regarding V1 and V2 behavior in the generic app start integration test and the native app start handler V2 test for clarity and to reflect current implementation.
packages/flutter/lib/src/integrations/native_app_start_handler_v2.dart
Outdated
Show resolved
Hide resolved
packages/flutter/lib/src/integrations/native_app_start_handler_v2.dart
Outdated
Show resolved
Hide resolved
packages/flutter/lib/src/integrations/native_app_start_integration.dart
Outdated
Show resolved
Hide resolved
packages/flutter/lib/src/navigation/sentry_navigator_observer.dart
Outdated
Show resolved
Hide resolved
packages/flutter/test/navigation/sentry_navigator_observer_test.dart
Outdated
Show resolved
Hide resolved
…AppStartHandlerV2 Streamlined the attribute assignment for span tracking by creating a single attributes map, reducing redundancy in the code. This change enhances maintainability and clarity in the NativeAppStartHandlerV2 implementation.
Added a check for null context in the NativeAppStartIntegration class to prevent potential errors. If the context is null, a warning is logged, and the integration process is skipped, enhancing robustness.
denrase
left a comment
There was a problem hiding this comment.
Added some more context to the questions, feel free to pick up if they make sense.
Updated method names in the time-to-display tracking system to improve clarity. Changed `trackRootNavigation` to `trackAppStart` and `trackNonRootNavigation` to `trackRoute`, aligning with their functionality. Adjusted related comments and test cases to reflect these changes, enhancing code readability and maintainability.
📜 Description
Ports the native app start and generic app start integrations to the V2 streaming span API, gated by
SentryTraceLifecycle.streaming.💡 Motivation and Context
Span-first and closes #3334
💚 How did you test it?
Unit tests, manual tests
📝 Checklist
sendDefaultPiiis enabled🔮 Next steps
Make sure mobile vitals insights product page works
#skip-changelog