diff --git a/CHANGELOG.md b/CHANGELOG.md index d3149156e2..5d8ab4cbbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features +- Add flag to disable reporting of view hierarchy identifiers ([#2158](https://github.com/getsentry/sentry-dart/pull/2158)) + - Use `reportViewHierarchyIdentifiers` to enable or disable the option - Record dropped spans in client reports ([#2154](https://github.com/getsentry/sentry-dart/pull/2154)) - Add memory usage to contexts ([#2133](https://github.com/getsentry/sentry-dart/pull/2133)) - Only for Linux/Windows applications, as iOS/Android/macOS use native SDKs diff --git a/flutter/lib/src/sentry_flutter_options.dart b/flutter/lib/src/sentry_flutter_options.dart index 913070e9ca..347da9ada3 100644 --- a/flutter/lib/src/sentry_flutter_options.dart +++ b/flutter/lib/src/sentry_flutter_options.dart @@ -213,6 +213,14 @@ class SentryFlutterOptions extends SentryOptions { @experimental bool attachViewHierarchy = false; + /// Enables collection of view hierarchy element identifiers. + /// + /// Identifiers are extracted from widget keys. + /// Disable this flag if your widget keys contain sensitive data. + /// + /// Default: `true` + bool reportViewHierarchyIdentifiers = true; + /// When enabled, the SDK tracks when the application stops responding for a /// specific amount of time, See [appHangTimeoutInterval]. /// Only available on iOS and macOS. diff --git a/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart b/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart index 41b03f4808..b2e759b2c9 100644 --- a/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart +++ b/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart @@ -209,9 +209,10 @@ import '../widget_utils.dart'; class _TreeWalker { static const _privateDelimiter = '_'; - _TreeWalker(this.rootElement); + _TreeWalker(this.rootElement, this.options); final Element rootElement; + final SentryFlutterOptions options; ValueChanged _visitor( SentryViewHierarchyElement parentSentryElement) { @@ -278,10 +279,15 @@ class _TreeWalker { alpha = widget.opacity; } + String? identifier; + if (options.reportViewHierarchyIdentifiers) { + identifier = WidgetUtils.toStringValue(widget.key); + } + return SentryViewHierarchyElement( element.widget.runtimeType.toString(), depth: element.depth, - identifier: WidgetUtils.toStringValue(element.widget.key), + identifier: identifier, width: width, height: height, x: x, @@ -292,7 +298,8 @@ class _TreeWalker { } } -SentryViewHierarchy? walkWidgetTree(WidgetsBinding instance) { +SentryViewHierarchy? walkWidgetTree( + WidgetsBinding instance, SentryFlutterOptions options) { // to keep compatibility with older versions // ignore: deprecated_member_use final rootElement = instance.renderViewElement; @@ -300,7 +307,7 @@ SentryViewHierarchy? walkWidgetTree(WidgetsBinding instance) { return null; } - final walker = _TreeWalker(rootElement); + final walker = _TreeWalker(rootElement, options); return walker.toSentryViewHierarchy(); } diff --git a/flutter/lib/src/view_hierarchy/view_hierarchy_event_processor.dart b/flutter/lib/src/view_hierarchy/view_hierarchy_event_processor.dart index c3fefe52f5..cf9ae008ec 100644 --- a/flutter/lib/src/view_hierarchy/view_hierarchy_event_processor.dart +++ b/flutter/lib/src/view_hierarchy/view_hierarchy_event_processor.dart @@ -23,7 +23,7 @@ class SentryViewHierarchyEventProcessor implements EventProcessor { if (instance == null) { return event; } - final sentryViewHierarchy = walkWidgetTree(instance); + final sentryViewHierarchy = walkWidgetTree(instance, _options); if (sentryViewHierarchy == null) { return event; diff --git a/flutter/test/view_hierarchy/sentry_tree_walker_test.dart b/flutter/test/view_hierarchy/sentry_tree_walker_test.dart index 52d775529b..9fd41fc522 100644 --- a/flutter/test/view_hierarchy/sentry_tree_walker_test.dart +++ b/flutter/test/view_hierarchy/sentry_tree_walker_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sentry/sentry.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/view_hierarchy/sentry_tree_walker.dart'; void main() { @@ -16,7 +16,8 @@ void main() { await tester.runAsync(() async { await tester.pumpWidget(MyApp()); - final sentryViewHierarchy = walkWidgetTree(instance); + final sentryViewHierarchy = + walkWidgetTree(instance, SentryFlutterOptions()); expect(sentryViewHierarchy!.renderingSystem, 'flutter'); }); @@ -147,7 +148,8 @@ void main() { SentryViewHierarchyElement _getFirstSentryViewHierarchy( WidgetsBinding instance) { - final sentryViewHierarchy = walkWidgetTree(instance); + final options = SentryFlutterOptions(); + final sentryViewHierarchy = walkWidgetTree(instance, options); return sentryViewHierarchy!.windows.first; } diff --git a/flutter/test/view_hierarchy/view_hierarchy_event_processor_test.dart b/flutter/test/view_hierarchy/view_hierarchy_event_processor_test.dart index 7743e8ebc6..98487ab3a2 100644 --- a/flutter/test/view_hierarchy/view_hierarchy_event_processor_test.dart +++ b/flutter/test/view_hierarchy/view_hierarchy_event_processor_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry/sentry.dart'; @@ -79,6 +81,27 @@ void main() { expect(hint.viewHierarchy, isNull); }); }); + + testWidgets('does not add view hierarchy identifiers if opt out in options', + (tester) async { + await tester.runAsync(() async { + final sut = + fixture.getSut(instance, reportViewHierarchyIdentifiers: false); + + await tester.pumpWidget(MyApp()); + + final event = SentryEvent( + exceptions: [SentryException(type: 'type', value: 'value')]); + final hint = Hint(); + + sut.apply(event, hint); + + expect(hint.viewHierarchy, isNotNull); + final bytes = await hint.viewHierarchy!.bytes; + final jsonString = utf8.decode(bytes); + expect(jsonString, isNot(contains('identifier'))); + }); + }); }); } @@ -99,9 +122,11 @@ class TestBindingWrapper implements BindingWrapper { } class Fixture { - SentryViewHierarchyEventProcessor getSut(WidgetsBinding instance) { + SentryViewHierarchyEventProcessor getSut(WidgetsBinding instance, + {bool reportViewHierarchyIdentifiers = true}) { final options = SentryFlutterOptions() - ..bindingUtils = TestBindingWrapper(instance); + ..bindingUtils = TestBindingWrapper(instance) + ..reportViewHierarchyIdentifiers = reportViewHierarchyIdentifiers; return SentryViewHierarchyEventProcessor(options); } }