diff --git a/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart b/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart index 03623d7f0b..47e3481144 100644 --- a/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart +++ b/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart @@ -7,13 +7,11 @@ EnricherEventProcessor enricherEventProcessor(SentryOptions options) { return IoEnricherEventProcessor(options); } -/// Enriches [SentryEvents] with various kinds of information. +/// Enriches [SentryEvent]s with various kinds of information. /// Uses Darts [Platform](https://api.dart.dev/stable/dart-io/Platform-class.html) /// class to read information. class IoEnricherEventProcessor implements EnricherEventProcessor { - IoEnricherEventProcessor( - this._options, - ); + IoEnricherEventProcessor(this._options); final SentryOptions _options; diff --git a/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart b/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart index fe2684d593..f7681eeeaf 100644 --- a/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart +++ b/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart @@ -50,9 +50,7 @@ class WebEnricherEventProcessor implements EnricherEventProcessor { final url = request?.url ?? _window.location.toString(); return (request ?? SentryRequest(url: url)) - .copyWith( - headers: header, - ) + .copyWith(headers: header) .sanitized(); } diff --git a/dart/lib/src/hint.dart b/dart/lib/src/hint.dart index aaa614518c..41ea7a0deb 100644 --- a/dart/lib/src/hint.dart +++ b/dart/lib/src/hint.dart @@ -1,4 +1,5 @@ import 'sentry_attachment/sentry_attachment.dart'; +import 'sentry_options.dart'; /// Hints are used in [BeforeSendCallback], [BeforeBreadcrumbCallback] and /// event processors. diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 709bda104f..be866d6719 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -24,7 +24,7 @@ import 'client_reports/client_report_recorder.dart'; import 'client_reports/discard_reason.dart'; import 'transport/data_category.dart'; -/// Default value for [User.ipAddress]. It gets set when an event does not have +/// Default value for [SentryUser.ipAddress]. It gets set when an event does not have /// a user and IP address. Only applies if [SentryOptions.sendDefaultPii] is set /// to true. const _defaultIpAddress = '{{auto}}'; diff --git a/dart/lib/src/sentry_stack_trace_factory.dart b/dart/lib/src/sentry_stack_trace_factory.dart index fe6b3cc88b..b7f0504bb9 100644 --- a/dart/lib/src/sentry_stack_trace_factory.dart +++ b/dart/lib/src/sentry_stack_trace_factory.dart @@ -5,7 +5,7 @@ import 'noop_origin.dart' if (dart.library.html) 'origin.dart'; import 'protocol.dart'; import 'sentry_options.dart'; -/// converts [StackTrace] to [SentryStackFrames] +/// converts [StackTrace] to [SentryStackFrame]s class SentryStackTraceFactory { final SentryOptions _options; @@ -21,6 +21,10 @@ class SentryStackTraceFactory { 'sentry_logging', 'sentry_dio', 'sentry_file', + 'sentry_hive', + 'sentry_isar', + 'sentry_sqflite', + 'sentry_drift', ]; SentryStackTraceFactory(this._options); @@ -123,16 +127,18 @@ class SentryStackTraceFactory { absPath: abs, function: member, // https://docs.sentry.io/development/sdk-dev/features/#in-app-frames - inApp: isInApp(frame), + inApp: _isInApp(frame), fileName: fileName, package: frame.package, ); - if (frame.line != null && frame.line! >= 0) { + final line = frame.line; + if (line != null && line >= 0) { sentryStackFrame = sentryStackFrame.copyWith(lineNo: frame.line); } - if (frame.column != null && frame.column! >= 0) { + final column = frame.column; + if (column != null && column >= 0) { sentryStackFrame = sentryStackFrame.copyWith(colNo: frame.column); } return sentryStackFrame; @@ -153,11 +159,11 @@ class SentryStackTraceFactory { return frame.uri.pathSegments.last; } - return '${frame.uri}'; + return frame.uri.toString(); } /// whether this frame comes from the app and not from Dart core or 3rd party librairies - bool isInApp(Frame frame) { + bool _isInApp(Frame frame) { final scheme = frame.uri.scheme; if (scheme.isEmpty) { diff --git a/dart/lib/src/sentry_tracer.dart b/dart/lib/src/sentry_tracer.dart index 6012a13bfb..c57912fe46 100644 --- a/dart/lib/src/sentry_tracer.dart +++ b/dart/lib/src/sentry_tracer.dart @@ -49,9 +49,9 @@ class SentryTracer extends ISentrySpan { /// highest timestamp of child spans, trimming the duration of the /// transaction. This is useful to discard extra time in the transaction that /// is not accounted for in child spans, like what happens in the - /// [SentryNavigatorObserver] idle transactions, where we finish the - /// transaction after a given "idle time" and we don't want this "idle time" - /// to be part of the transaction. + /// [SentryNavigatorObserver](https://pub.dev/documentation/sentry_flutter/latest/sentry_flutter/SentryNavigatorObserver-class.html) + /// idle transactions, where we finish the transaction after a given + /// "idle time" and we don't want this "idle time" to be part of the transaction. SentryTracer( SentryTransactionContext transactionContext, this._hub, { diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 55b9072394..6a0c0a5b81 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -42,52 +42,58 @@ final GlobalKey navigatorKey = GlobalKey(); Future main() async { await setupSentry( - () => runApp( - SentryWidget( - child: DefaultAssetBundle( - bundle: SentryAssetBundle(), - child: const MyApp(), - ), - ), - ), - exampleDsn); + () => runApp( + SentryWidget( + child: DefaultAssetBundle( + bundle: SentryAssetBundle(), + child: const MyApp(), + ), + ), + ), + exampleDsn, + ); } -Future setupSentry(AppRunner appRunner, String dsn, - {bool isIntegrationTest = false, - BeforeSendCallback? beforeSendCallback}) async { - await SentryFlutter.init((options) { - options.dsn = exampleDsn; - options.tracesSampleRate = 1.0; - options.profilesSampleRate = 1.0; - options.reportPackages = false; - options.addInAppInclude('sentry_flutter_example'); - options.considerInAppFramesByDefault = false; - options.attachThreads = true; - options.enableWindowMetricBreadcrumbs = true; - options.addIntegration(LoggingIntegration(minEventLevel: Level.INFO)); - options.sendDefaultPii = true; - options.reportSilentFlutterErrors = true; - options.attachScreenshot = true; - options.screenshotQuality = SentryScreenshotQuality.low; - options.attachViewHierarchy = true; - // We can enable Sentry debug logging during development. This is likely - // going to log too much for your app, but can be useful when figuring out - // configuration issues, e.g. finding out why your events are not uploaded. - options.debug = true; - - options.maxRequestBodySize = MaxRequestBodySize.always; - options.maxResponseBodySize = MaxResponseBodySize.always; - - _isIntegrationTest = isIntegrationTest; - if (_isIntegrationTest) { - options.dist = '1'; - options.environment = 'integration'; - options.beforeSend = beforeSendCallback; - } - }, - // Init your App. - appRunner: appRunner); +Future setupSentry( + AppRunner appRunner, + String dsn, { + bool isIntegrationTest = false, + BeforeSendCallback? beforeSendCallback, +}) async { + await SentryFlutter.init( + (options) { + options.dsn = exampleDsn; + options.tracesSampleRate = 1.0; + options.profilesSampleRate = 1.0; + options.reportPackages = false; + options.addInAppInclude('sentry_flutter_example'); + options.considerInAppFramesByDefault = false; + options.attachThreads = true; + options.enableWindowMetricBreadcrumbs = true; + options.addIntegration(LoggingIntegration(minEventLevel: Level.INFO)); + options.sendDefaultPii = true; + options.reportSilentFlutterErrors = true; + options.attachScreenshot = true; + options.screenshotQuality = SentryScreenshotQuality.low; + options.attachViewHierarchy = true; + // We can enable Sentry debug logging during development. This is likely + // going to log too much for your app, but can be useful when figuring out + // configuration issues, e.g. finding out why your events are not uploaded. + options.debug = true; + + options.maxRequestBodySize = MaxRequestBodySize.always; + options.maxResponseBodySize = MaxResponseBodySize.always; + + _isIntegrationTest = isIntegrationTest; + if (_isIntegrationTest) { + options.dist = '1'; + options.environment = 'integration'; + options.beforeSend = beforeSendCallback; + } + }, + // Init your App. + appRunner: appRunner, + ); } class MyApp extends StatefulWidget { @@ -123,22 +129,23 @@ class TooltipButton extends StatelessWidget { final String buttonTitle; final void Function()? onPressed; - const TooltipButton( - {required this.onPressed, - required this.buttonTitle, - required this.text, - Key? key}) - : super(key: key); + const TooltipButton({ + required this.onPressed, + required this.buttonTitle, + required this.text, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { return Tooltip( - message: text, - child: ElevatedButton( - onPressed: onPressed, - key: key, - child: Text(buttonTitle), - )); + message: text, + child: ElevatedButton( + onPressed: onPressed, + key: key, + child: Text(buttonTitle), + ), + ); } } @@ -188,8 +195,9 @@ class MainScaffold extends StatelessWidget { const Padding( padding: EdgeInsets.all(15), //apply padding to all four sides child: Center( - child: Text( - 'Long press a button to see more information. (hover on web)')), + child: Text( + 'Long press a button to see more information. (hover on web)'), + ), ), TooltipButton( onPressed: () => navigateToAutoCloseScreen(context), @@ -301,20 +309,23 @@ class MainScaffold extends StatelessWidget { TooltipButton( onPressed: () { // modeled after a real exception - FlutterError.onError?.call(FlutterErrorDetails( - exception: Exception('A really bad exception'), - silent: false, - context: DiagnosticsNode.message('while handling a gesture'), - library: 'gesture', - informationCollector: () => [ - DiagnosticsNode.message( - 'Handler: "onTap" Recognizer: TapGestureRecognizer'), - DiagnosticsNode.message( - 'Handler: "onTap" Recognizer: TapGestureRecognizer'), - DiagnosticsNode.message( - 'Handler: "onTap" Recognizer: TapGestureRecognizer'), - ], - )); + FlutterError.onError?.call( + FlutterErrorDetails( + exception: Exception('A really bad exception'), + silent: false, + context: + DiagnosticsNode.message('while handling a gesture'), + library: 'gesture', + informationCollector: () => [ + DiagnosticsNode.message( + 'Handler: "onTap" Recognizer: TapGestureRecognizer'), + DiagnosticsNode.message( + 'Handler: "onTap" Recognizer: TapGestureRecognizer'), + DiagnosticsNode.message( + 'Handler: "onTap" Recognizer: TapGestureRecognizer'), + ], + ), + ); }, text: 'Creates a FlutterError and passes it to FlutterError.onError callback. This demonstrates how our flutter error integration catches unhandled exceptions.', @@ -449,27 +460,28 @@ class MainScaffold extends StatelessWidget { ), TooltipButton( onPressed: () { - feedback.BetterFeedback.of(context) - .show((feedback.UserFeedback feedback) { - Sentry.captureMessage( - feedback.text, - withScope: (scope) { - final entries = feedback.extra?.entries; - if (entries != null) { - for (final extra in entries) { - scope.setExtra(extra.key, extra.value); + feedback.BetterFeedback.of(context).show( + (feedback.UserFeedback feedback) { + Sentry.captureMessage( + feedback.text, + withScope: (scope) { + final entries = feedback.extra?.entries; + if (entries != null) { + for (final extra in entries) { + scope.setExtra(extra.key, extra.value); + } } - } - scope.addAttachment( - SentryAttachment.fromUint8List( - feedback.screenshot, - 'feedback.png', - contentType: 'image/png', - ), - ); - }, - ); - }); + scope.addAttachment( + SentryAttachment.fromUint8List( + feedback.screenshot, + 'feedback.png', + contentType: 'image/png', + ), + ); + }, + ); + }, + ); }, text: 'Sends the capture message with an image attachment to Sentry.', @@ -754,18 +766,20 @@ class _IntegrationTestWidgetState extends State { @override Widget build(BuildContext context) { - return Column(children: [ - Text( - _output, - key: const Key('output'), - ), - _isLoading - ? const CircularProgressIndicator() - : ElevatedButton( - onPressed: () async => await _captureException(), - child: const Text('captureException'), - ) - ]); + return Column( + children: [ + Text( + _output, + key: const Key('output'), + ), + _isLoading + ? const CircularProgressIndicator() + : ElevatedButton( + onPressed: () async => await _captureException(), + child: const Text('captureException'), + ) + ], + ); } Future _captureException() async { diff --git a/flutter/lib/src/integrations/screenshot_integration.dart b/flutter/lib/src/integrations/screenshot_integration.dart index d8d1adbc34..10cf60228a 100644 --- a/flutter/lib/src/integrations/screenshot_integration.dart +++ b/flutter/lib/src/integrations/screenshot_integration.dart @@ -2,7 +2,8 @@ import 'package:sentry/sentry.dart'; import '../event_processor/screenshot_event_processor.dart'; import '../sentry_flutter_options.dart'; -/// Adds [ScreenshotEventProcessor] to options event processors if [attachScreenshot] is true +/// Adds [ScreenshotEventProcessor] to options event processors if +/// [SentryFlutterOptions.attachScreenshot] is true class ScreenshotIntegration implements Integration { SentryFlutterOptions? _options; ScreenshotEventProcessor? _screenshotEventProcessor; diff --git a/flutter/lib/src/navigation/sentry_navigator_observer.dart b/flutter/lib/src/navigation/sentry_navigator_observer.dart index b24f4f0c7b..30beaa75bc 100644 --- a/flutter/lib/src/navigation/sentry_navigator_observer.dart +++ b/flutter/lib/src/navigation/sentry_navigator_observer.dart @@ -44,21 +44,22 @@ typedef AdditionalInfoExtractor = Map? Function( /// ) /// ``` /// -/// The option [enableAutoTransactions] is enabled by default. For every new -/// route a transaction is started. It's automatically finished after -/// [autoFinishAfter] duration or when all child spans are finished, -/// if those happen to take longer. The transaction will be set to [Scope.span] -/// if the latter is empty. -/// -/// Enabling the [setRouteNameAsTransaction] option overrides the current -/// [Scope.transaction] which will also override the name of the current -/// [Scope.span]. So be careful when this is used together with performance -/// monitoring. +/// See the constructor docs for the argument documentation. /// /// See also: /// - [RouteObserver](https://api.flutter.dev/flutter/widgets/RouteObserver-class.html) /// - [Navigating with arguments](https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments) class SentryNavigatorObserver extends RouteObserver> { + /// The option [enableAutoTransactions] is enabled by default. + /// For every new route a transaction is started. It's automatically finished + /// after [autoFinishAfter] duration or when all child spans are + /// finished, if those happen to take longer. + /// The transaction will be set to [Scope.span] if the latter is empty. + /// + /// Enabling the [setRouteNameAsTransaction] option overrides the + /// current [Scope.transaction] which will also override the name of the current + /// [Scope.span]. So be careful when this is used together with performance + /// monitoring. SentryNavigatorObserver({ Hub? hub, bool enableAutoTransactions = true, diff --git a/flutter/lib/src/sentry_flutter_options.dart b/flutter/lib/src/sentry_flutter_options.dart index ee722f8e9e..ceda7ef0cc 100644 --- a/flutter/lib/src/sentry_flutter_options.dart +++ b/flutter/lib/src/sentry_flutter_options.dart @@ -8,6 +8,9 @@ import 'binding_wrapper.dart'; import 'renderer/renderer.dart'; import 'screenshot/sentry_screenshot_quality.dart'; import 'event_processor/screenshot_event_processor.dart'; +import 'screenshot/sentry_screenshot_widget.dart'; +import 'sentry_flutter.dart'; +import 'user_interaction/sentry_user_interaction_widget.dart'; /// This class adds options which are only available in a Flutter environment. /// Note that some of these options require native Sentry integration, which is