From 5e2621475fe1db42db322ae1b252203b9e430a90 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 15 Jul 2024 15:54:57 +0200 Subject: [PATCH 01/24] try to mitigate runtime type not being obfuscated --- dart/lib/sentry.dart | 57 ++++++++++--------- dart/lib/src/dart_error_type_identifier.dart | 19 +++++++ dart/lib/src/error_type_identifier.dart | 3 + dart/lib/src/sentry_client.dart | 31 ++++++---- dart/lib/src/sentry_exception_factory.dart | 16 ++++-- dart/lib/src/sentry_options.dart | 17 +++++- .../src/flutter_error_type_identifier.dart | 17 ++++++ flutter/lib/src/sentry_flutter.dart | 15 ++--- 8 files changed, 121 insertions(+), 54 deletions(-) create mode 100644 dart/lib/src/dart_error_type_identifier.dart create mode 100644 dart/lib/src/error_type_identifier.dart create mode 100644 flutter/lib/src/flutter_error_type_identifier.dart diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index fd88a49fc5..08189a5396 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -5,53 +5,54 @@ /// A pure Dart client for Sentry.io crash reporting. library sentry_dart; -export 'src/run_zoned_guarded_integration.dart'; +export 'src/error_type_identifier.dart'; +export 'src/event_processor.dart'; +export 'src/exception_cause.dart'; +// exception extraction +export 'src/exception_cause_extractor.dart'; +export 'src/exception_stacktrace_extractor.dart'; +export 'src/hint.dart'; +export 'src/http_client/sentry_http_client.dart'; +export 'src/http_client/sentry_http_client_error.dart'; export 'src/hub.dart'; // useful for tests export 'src/hub_adapter.dart'; -export 'src/platform_checker.dart'; +export 'src/integration.dart'; export 'src/noop_isolate_error_integration.dart' if (dart.library.io) 'src/isolate_error_integration.dart'; +export 'src/performance_collector.dart'; +export 'src/platform_checker.dart'; export 'src/protocol.dart'; +export 'src/run_zoned_guarded_integration.dart'; export 'src/scope.dart'; export 'src/scope_observer.dart'; export 'src/sentry.dart'; +export 'src/sentry_attachment/sentry_attachment.dart'; +export 'src/sentry_baggage.dart'; +export 'src/sentry_client.dart'; export 'src/sentry_envelope.dart'; export 'src/sentry_envelope_item.dart'; -export 'src/sentry_client.dart'; export 'src/sentry_options.dart'; +// ignore: invalid_export_of_internal_element +export 'src/sentry_span_operations.dart'; +// ignore: invalid_export_of_internal_element +export 'src/sentry_trace_origins.dart'; +export 'src/sentry_user_feedback.dart'; +// spotlight debugging +export 'src/spotlight.dart'; // useful for integrations export 'src/throwable_mechanism.dart'; -export 'src/transport/transport.dart'; -export 'src/integration.dart'; -export 'src/event_processor.dart'; -export 'src/http_client/sentry_http_client.dart'; -export 'src/http_client/sentry_http_client_error.dart'; -export 'src/sentry_attachment/sentry_attachment.dart'; -export 'src/sentry_user_feedback.dart'; -export 'src/utils/tracing_utils.dart'; -export 'src/performance_collector.dart'; // tracing export 'src/tracing.dart'; -export 'src/hint.dart'; +export 'src/transport/transport.dart'; export 'src/type_check_hint.dart'; -export 'src/sentry_baggage.dart'; -// exception extraction -export 'src/exception_cause_extractor.dart'; -export 'src/exception_cause.dart'; -export 'src/exception_stacktrace_extractor.dart'; -// URL // ignore: invalid_export_of_internal_element -export 'src/utils/http_sanitizer.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils/url_details.dart'; +export 'src/utils.dart'; // ignore: invalid_export_of_internal_element export 'src/utils/http_header_utils.dart'; +// URL // ignore: invalid_export_of_internal_element -export 'src/sentry_trace_origins.dart'; -// ignore: invalid_export_of_internal_element -export 'src/sentry_span_operations.dart'; +export 'src/utils/http_sanitizer.dart'; +export 'src/utils/tracing_utils.dart'; // ignore: invalid_export_of_internal_element -export 'src/utils.dart'; -// spotlight debugging -export 'src/spotlight.dart'; +export 'src/utils/url_details.dart'; diff --git a/dart/lib/src/dart_error_type_identifier.dart b/dart/lib/src/dart_error_type_identifier.dart new file mode 100644 index 0000000000..3841ba0fe5 --- /dev/null +++ b/dart/lib/src/dart_error_type_identifier.dart @@ -0,0 +1,19 @@ +import '../sentry.dart'; + +class DartErrorIdentifier implements ErrorTypeIdentifier { + @override + String? getTypeName(dynamic error) { + if (error is NoSuchMethodError) return 'NoSuchMethodError'; + if (error is FormatException) return 'FormatException'; + if (error is TypeError) return 'TypeError'; + if (error is ArgumentError) return 'ArgumentError'; + if (error is StateError) return 'StateError'; + if (error is UnsupportedError) return 'UnsupportedError'; + if (error is UnimplementedError) return 'UnimplementedError'; + if (error is ConcurrentModificationError) + return 'ConcurrentModificationError'; + if (error is OutOfMemoryError) return 'OutOfMemoryError'; + if (error is StackOverflowError) return 'StackOverflowError'; + return null; + } +} diff --git a/dart/lib/src/error_type_identifier.dart b/dart/lib/src/error_type_identifier.dart new file mode 100644 index 0000000000..48b0024c28 --- /dev/null +++ b/dart/lib/src/error_type_identifier.dart @@ -0,0 +1,3 @@ +abstract class ErrorTypeIdentifier { + String? getTypeName(dynamic error); +} diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 57c1bd9326..164299530d 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -1,32 +1,33 @@ import 'dart:async'; import 'dart:math'; + import 'package:meta/meta.dart'; -import 'utils/stacktrace_utils.dart'; -import 'metrics/metric.dart'; -import 'metrics/metrics_aggregator.dart'; -import 'sentry_baggage.dart'; -import 'sentry_attachment/sentry_attachment.dart'; +import 'client_reports/client_report_recorder.dart'; +import 'client_reports/discard_reason.dart'; import 'event_processor.dart'; import 'hint.dart'; -import 'sentry_trace_context_header.dart'; -import 'sentry_user_feedback.dart'; -import 'transport/rate_limiter.dart'; +import 'metrics/metric.dart'; +import 'metrics/metrics_aggregator.dart'; import 'protocol.dart'; import 'scope.dart'; +import 'sentry_attachment/sentry_attachment.dart'; +import 'sentry_baggage.dart'; +import 'sentry_envelope.dart'; import 'sentry_exception_factory.dart'; import 'sentry_options.dart'; import 'sentry_stack_trace_factory.dart'; +import 'sentry_trace_context_header.dart'; +import 'sentry_user_feedback.dart'; +import 'transport/data_category.dart'; import 'transport/http_transport.dart'; import 'transport/noop_transport.dart'; +import 'transport/rate_limiter.dart'; import 'transport/spotlight_http_transport.dart'; import 'transport/task_queue.dart'; import 'utils/isolate_utils.dart'; +import 'utils/stacktrace_utils.dart'; import 'version.dart'; -import 'sentry_envelope.dart'; -import 'client_reports/client_report_recorder.dart'; -import 'client_reports/discard_reason.dart'; -import 'transport/data_category.dart'; /// 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 @@ -183,6 +184,8 @@ class SentryClient { return event; } + print('omgg2'); + print(event.exceptions); if (event.exceptions?.isNotEmpty ?? false) { return event; } @@ -192,9 +195,13 @@ class SentryClient { final isolateId = isolateName?.hashCode; if (event.throwableMechanism != null) { + print('omgghallo'); final extractedExceptions = _exceptionFactory.extractor .flatten(event.throwableMechanism, stackTrace); + print('extractedExceptions'); + print(extractedExceptions.first.exception); + final sentryExceptions = []; final sentryThreads = []; diff --git a/dart/lib/src/sentry_exception_factory.dart b/dart/lib/src/sentry_exception_factory.dart index a8e1a80498..6c24285f05 100644 --- a/dart/lib/src/sentry_exception_factory.dart +++ b/dart/lib/src/sentry_exception_factory.dart @@ -1,10 +1,9 @@ -import 'utils/stacktrace_utils.dart'; - -import 'recursive_exception_cause_extractor.dart'; import 'protocol.dart'; +import 'recursive_exception_cause_extractor.dart'; import 'sentry_options.dart'; import 'sentry_stack_trace_factory.dart'; import 'throwable_mechanism.dart'; +import 'utils/stacktrace_utils.dart'; /// class to convert Dart Error and exception to SentryException class SentryExceptionFactory { @@ -62,10 +61,19 @@ class SentryExceptionFactory { final stackTraceString = stackTrace.toString(); final value = throwableString.replaceAll(stackTraceString, '').trim(); + String type = throwable.runtimeType.toString(); + for (final identifier in _options.errorTypeIdentifiers) { + final typeName = identifier.getTypeName(throwable); + if (typeName != null) { + type = typeName; + break; + } + } + // if --obfuscate feature is enabled, 'type' won't be human readable. // https://flutter.dev/docs/deployment/obfuscate#caveat return SentryException( - type: (throwable.runtimeType).toString(), + type: type, value: value.isNotEmpty ? value : null, mechanism: mechanism, stackTrace: sentryStackTrace, diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index f3f3c65a50..af2c2a6bab 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -1,17 +1,18 @@ import 'dart:async'; import 'dart:developer'; -import 'package:meta/meta.dart'; import 'package:http/http.dart'; +import 'package:meta/meta.dart'; import '../sentry.dart'; import 'client_reports/client_report_recorder.dart'; import 'client_reports/noop_client_report_recorder.dart'; -import 'sentry_exception_factory.dart'; -import 'sentry_stack_trace_factory.dart'; +import 'dart_error_type_identifier.dart'; import 'diagnostic_logger.dart'; import 'environment/environment_variables.dart'; import 'noop_client.dart'; +import 'sentry_exception_factory.dart'; +import 'sentry_stack_trace_factory.dart'; import 'transport/noop_transport.dart'; import 'version.dart'; @@ -436,6 +437,16 @@ class SentryOptions { /// Settings this to `false` will set the `level` to [SentryLevel.error]. bool markAutomaticallyCollectedErrorsAsFatal = true; + final List _errorTypeIdentifiers = [ + DartErrorIdentifier(), + ]; + + List get errorTypeIdentifiers => _errorTypeIdentifiers; + + void addErrorTypeIdentifier(ErrorTypeIdentifier errorTypeIdentifier) { + _errorTypeIdentifiers.add(errorTypeIdentifier); + } + /// The Spotlight configuration. /// Disabled by default. /// ```dart diff --git a/flutter/lib/src/flutter_error_type_identifier.dart b/flutter/lib/src/flutter_error_type_identifier.dart new file mode 100644 index 0000000000..e974eea373 --- /dev/null +++ b/flutter/lib/src/flutter_error_type_identifier.dart @@ -0,0 +1,17 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; + +import '../sentry_flutter.dart'; + +class FlutterErrorIdentifier implements ErrorTypeIdentifier { + @override + String? getTypeName(dynamic error) { + if (error is FlutterError) return 'FlutterError'; + if (error is PlatformException) return 'PlatformException'; + if (error is MissingPluginException) return 'MissingPluginException'; + if (error is AssertionError) return 'AssertionError'; + if (error is NetworkImageLoadException) return 'NetworkImageLoadException'; + if (error is TickerCanceled) return 'TickerCanceled'; + return null; + } +} diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 4b0deb8541..079c4cab11 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -3,26 +3,25 @@ import 'dart:ui'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; -import 'span_frame_metrics_collector.dart'; + import '../sentry_flutter.dart'; import 'event_processor/android_platform_exception_event_processor.dart'; +import 'event_processor/flutter_enricher_event_processor.dart'; import 'event_processor/flutter_exception_event_processor.dart'; import 'event_processor/platform_exception_event_processor.dart'; import 'event_processor/widget_event_processor.dart'; +import 'file_system_transport.dart'; +import 'flutter_error_type_identifier.dart'; import 'frame_callback_handler.dart'; import 'integrations/connectivity/connectivity_integration.dart'; +import 'integrations/integrations.dart'; import 'integrations/screenshot_integration.dart'; import 'native/factory.dart'; import 'native/native_scope_observer.dart'; import 'native/sentry_native_binding.dart'; import 'profiling.dart'; import 'renderer/renderer.dart'; - -import 'integrations/integrations.dart'; -import 'event_processor/flutter_enricher_event_processor.dart'; - -import 'file_system_transport.dart'; - +import 'span_frame_metrics_collector.dart'; import 'version.dart'; import 'view_hierarchy/view_hierarchy_integration.dart'; @@ -138,6 +137,8 @@ mixin SentryFlutter { options.addPerformanceCollector(SpanFrameMetricsCollector(options)); + options.addErrorTypeIdentifier(FlutterErrorIdentifier()); + _setSdk(options); } From c3d307f2e0d0eac101289436ff1d3d14ce3f568e Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 15 Jul 2024 15:57:21 +0200 Subject: [PATCH 02/24] fix imports --- dart/lib/sentry.dart | 58 ++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index 08189a5396..6d7bb27323 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -5,54 +5,54 @@ /// A pure Dart client for Sentry.io crash reporting. library sentry_dart; -export 'src/error_type_identifier.dart'; -export 'src/event_processor.dart'; -export 'src/exception_cause.dart'; -// exception extraction -export 'src/exception_cause_extractor.dart'; -export 'src/exception_stacktrace_extractor.dart'; -export 'src/hint.dart'; -export 'src/http_client/sentry_http_client.dart'; -export 'src/http_client/sentry_http_client_error.dart'; +export 'src/run_zoned_guarded_integration.dart'; export 'src/hub.dart'; // useful for tests export 'src/hub_adapter.dart'; -export 'src/integration.dart'; +export 'src/platform_checker.dart'; export 'src/noop_isolate_error_integration.dart' if (dart.library.io) 'src/isolate_error_integration.dart'; -export 'src/performance_collector.dart'; -export 'src/platform_checker.dart'; export 'src/protocol.dart'; -export 'src/run_zoned_guarded_integration.dart'; export 'src/scope.dart'; export 'src/scope_observer.dart'; export 'src/sentry.dart'; -export 'src/sentry_attachment/sentry_attachment.dart'; -export 'src/sentry_baggage.dart'; -export 'src/sentry_client.dart'; export 'src/sentry_envelope.dart'; export 'src/sentry_envelope_item.dart'; +export 'src/sentry_client.dart'; export 'src/sentry_options.dart'; -// ignore: invalid_export_of_internal_element -export 'src/sentry_span_operations.dart'; -// ignore: invalid_export_of_internal_element -export 'src/sentry_trace_origins.dart'; -export 'src/sentry_user_feedback.dart'; -// spotlight debugging -export 'src/spotlight.dart'; // useful for integrations export 'src/throwable_mechanism.dart'; +export 'src/transport/transport.dart'; +export 'src/integration.dart'; +export 'src/event_processor.dart'; +export 'src/http_client/sentry_http_client.dart'; +export 'src/http_client/sentry_http_client_error.dart'; +export 'src/sentry_attachment/sentry_attachment.dart'; +export 'src/sentry_user_feedback.dart'; +export 'src/utils/tracing_utils.dart'; +export 'src/performance_collector.dart'; // tracing export 'src/tracing.dart'; -export 'src/transport/transport.dart'; +export 'src/hint.dart'; export 'src/type_check_hint.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils/http_header_utils.dart'; +export 'src/sentry_baggage.dart'; +// exception extraction +export 'src/exception_cause_extractor.dart'; +export 'src/exception_cause.dart'; +export 'src/exception_stacktrace_extractor.dart'; +export 'src/error_type_identifier.dart'; // URL // ignore: invalid_export_of_internal_element export 'src/utils/http_sanitizer.dart'; -export 'src/utils/tracing_utils.dart'; // ignore: invalid_export_of_internal_element export 'src/utils/url_details.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils/http_header_utils.dart'; +// ignore: invalid_export_of_internal_element +export 'src/sentry_trace_origins.dart'; +// ignore: invalid_export_of_internal_element +export 'src/sentry_span_operations.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils.dart'; +// spotlight debugging +export 'src/spotlight.dart'; From aa007d4042b32c44fc4f574ec46891cb4621283f Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 15 Jul 2024 15:58:21 +0200 Subject: [PATCH 03/24] Remove prints --- dart/lib/src/sentry_client.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 164299530d..04b6425134 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -184,8 +184,6 @@ class SentryClient { return event; } - print('omgg2'); - print(event.exceptions); if (event.exceptions?.isNotEmpty ?? false) { return event; } @@ -195,13 +193,9 @@ class SentryClient { final isolateId = isolateName?.hashCode; if (event.throwableMechanism != null) { - print('omgghallo'); final extractedExceptions = _exceptionFactory.extractor .flatten(event.throwableMechanism, stackTrace); - print('extractedExceptions'); - print(extractedExceptions.first.exception); - final sentryExceptions = []; final sentryThreads = []; From 526102e592a4ee40ceefedd6a56faeff362b17f6 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 15 Jul 2024 15:59:34 +0200 Subject: [PATCH 04/24] Update --- dart/lib/src/sentry_exception_factory.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dart/lib/src/sentry_exception_factory.dart b/dart/lib/src/sentry_exception_factory.dart index 6c24285f05..0c4a61b8fd 100644 --- a/dart/lib/src/sentry_exception_factory.dart +++ b/dart/lib/src/sentry_exception_factory.dart @@ -61,11 +61,11 @@ class SentryExceptionFactory { final stackTraceString = stackTrace.toString(); final value = throwableString.replaceAll(stackTraceString, '').trim(); - String type = throwable.runtimeType.toString(); - for (final identifier in _options.errorTypeIdentifiers) { - final typeName = identifier.getTypeName(throwable); - if (typeName != null) { - type = typeName; + String errorTypeName = throwable.runtimeType.toString(); + for (final errorTypeIdentifier in _options.errorTypeIdentifiers) { + final identifiedErrorType = errorTypeIdentifier.getTypeName(throwable); + if (identifiedErrorType != null) { + errorTypeName = identifiedErrorType; break; } } @@ -73,7 +73,7 @@ class SentryExceptionFactory { // if --obfuscate feature is enabled, 'type' won't be human readable. // https://flutter.dev/docs/deployment/obfuscate#caveat return SentryException( - type: type, + type: errorTypeName, value: value.isNotEmpty ? value : null, mechanism: mechanism, stackTrace: sentryStackTrace, From 745a18f4f5b7c5010fa0363067eb7565dd17eec2 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 16 Jul 2024 16:05:45 +0200 Subject: [PATCH 05/24] Update --- dart/lib/sentry.dart | 2 +- ...rt => dart_exception_type_identifier.dart} | 20 +++++++----- dart/lib/src/error_type_identifier.dart | 3 -- dart/lib/src/exception_type_identifier.dart | 25 +++++++++++++++ dart/lib/src/sentry_exception_factory.dart | 4 +-- dart/lib/src/sentry_options.dart | 14 ++++---- .../src/flutter_error_type_identifier.dart | 32 ++++++++++++++----- flutter/lib/src/sentry_flutter.dart | 2 +- 8 files changed, 73 insertions(+), 29 deletions(-) rename dart/lib/src/{dart_error_type_identifier.dart => dart_exception_type_identifier.dart} (66%) delete mode 100644 dart/lib/src/error_type_identifier.dart create mode 100644 dart/lib/src/exception_type_identifier.dart diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index 6d7bb27323..a43fc08fc6 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -40,7 +40,7 @@ export 'src/sentry_baggage.dart'; export 'src/exception_cause_extractor.dart'; export 'src/exception_cause.dart'; export 'src/exception_stacktrace_extractor.dart'; -export 'src/error_type_identifier.dart'; +export 'src/exception_type_identifier.dart'; // URL // ignore: invalid_export_of_internal_element export 'src/utils/http_sanitizer.dart'; diff --git a/dart/lib/src/dart_error_type_identifier.dart b/dart/lib/src/dart_exception_type_identifier.dart similarity index 66% rename from dart/lib/src/dart_error_type_identifier.dart rename to dart/lib/src/dart_exception_type_identifier.dart index 3841ba0fe5..efd0c568e3 100644 --- a/dart/lib/src/dart_error_type_identifier.dart +++ b/dart/lib/src/dart_exception_type_identifier.dart @@ -1,19 +1,23 @@ import '../sentry.dart'; -class DartErrorIdentifier implements ErrorTypeIdentifier { +class DartExceptionTypeIdentifier implements ExceptionTypeIdentifier { @override - String? getTypeName(dynamic error) { - if (error is NoSuchMethodError) return 'NoSuchMethodError'; - if (error is FormatException) return 'FormatException'; - if (error is TypeError) return 'TypeError'; + String? identifyType(dynamic error) { if (error is ArgumentError) return 'ArgumentError'; - if (error is StateError) return 'StateError'; - if (error is UnsupportedError) return 'UnsupportedError'; - if (error is UnimplementedError) return 'UnimplementedError'; + if (error is AssertionError) return 'AssertionError'; if (error is ConcurrentModificationError) return 'ConcurrentModificationError'; + if (error is FormatException) return 'FormatException'; + if (error is IndexError) return 'IndexError'; + if (error is NoSuchMethodError) return 'NoSuchMethodError'; if (error is OutOfMemoryError) return 'OutOfMemoryError'; + if (error is RangeError) return 'RangeError'; if (error is StackOverflowError) return 'StackOverflowError'; + if (error is StateError) return 'StateError'; + if (error is TypeError) return 'TypeError'; + if (error is UnimplementedError) return 'UnimplementedError'; + if (error is UnsupportedError) return 'UnsupportedError'; + // we purposefully don't include Exception or Error since it's too generic return null; } } diff --git a/dart/lib/src/error_type_identifier.dart b/dart/lib/src/error_type_identifier.dart deleted file mode 100644 index 48b0024c28..0000000000 --- a/dart/lib/src/error_type_identifier.dart +++ /dev/null @@ -1,3 +0,0 @@ -abstract class ErrorTypeIdentifier { - String? getTypeName(dynamic error); -} diff --git a/dart/lib/src/exception_type_identifier.dart b/dart/lib/src/exception_type_identifier.dart new file mode 100644 index 0000000000..a06adda146 --- /dev/null +++ b/dart/lib/src/exception_type_identifier.dart @@ -0,0 +1,25 @@ +/// An abstract class for identifying the type of Dart errors and exceptions. +/// +/// This class provides a contract for implementing error type identification +/// in Dart. It's used in scenarios where error types need to be +/// determined in obfuscated builds, as [runtimeType] is not reliable in +/// such cases. +/// +/// Implement this class to create custom error type identifiers for errors or exceptions. +/// that we do not support out of the box. +/// +/// Add the implementation using [SentryOptions.addExceptionTypeIdentifier]. +/// +/// Example: +/// ```dart +/// class MyErrorTypeIdentifier implements ErrorTypeIdentifier { +/// @override +/// String? identifyType(dynamic error) { +/// if (error is MyCustomError) return 'MyCustomError'; +/// return null; +/// } +/// } +/// ``` +abstract class ExceptionTypeIdentifier { + String? identifyType(dynamic throwable); +} diff --git a/dart/lib/src/sentry_exception_factory.dart b/dart/lib/src/sentry_exception_factory.dart index 0c4a61b8fd..dceeb8753e 100644 --- a/dart/lib/src/sentry_exception_factory.dart +++ b/dart/lib/src/sentry_exception_factory.dart @@ -62,8 +62,8 @@ class SentryExceptionFactory { final value = throwableString.replaceAll(stackTraceString, '').trim(); String errorTypeName = throwable.runtimeType.toString(); - for (final errorTypeIdentifier in _options.errorTypeIdentifiers) { - final identifiedErrorType = errorTypeIdentifier.getTypeName(throwable); + for (final errorTypeIdentifier in _options.exceptionTypeIdentifiers) { + final identifiedErrorType = errorTypeIdentifier.identifyType(throwable); if (identifiedErrorType != null) { errorTypeName = identifiedErrorType; break; diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index af2c2a6bab..f0acc5be97 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -7,7 +7,7 @@ import 'package:meta/meta.dart'; import '../sentry.dart'; import 'client_reports/client_report_recorder.dart'; import 'client_reports/noop_client_report_recorder.dart'; -import 'dart_error_type_identifier.dart'; +import 'dart_exception_type_identifier.dart'; import 'diagnostic_logger.dart'; import 'environment/environment_variables.dart'; import 'noop_client.dart'; @@ -437,14 +437,16 @@ class SentryOptions { /// Settings this to `false` will set the `level` to [SentryLevel.error]. bool markAutomaticallyCollectedErrorsAsFatal = true; - final List _errorTypeIdentifiers = [ - DartErrorIdentifier(), + final List _exceptionTypeIdentifiers = [ + DartExceptionTypeIdentifier(), ]; - List get errorTypeIdentifiers => _errorTypeIdentifiers; + List get exceptionTypeIdentifiers => + _exceptionTypeIdentifiers; - void addErrorTypeIdentifier(ErrorTypeIdentifier errorTypeIdentifier) { - _errorTypeIdentifiers.add(errorTypeIdentifier); + void addExceptionTypeIdentifier( + ExceptionTypeIdentifier exceptionTypeIdentifier) { + _exceptionTypeIdentifiers.insert(0, exceptionTypeIdentifier); } /// The Spotlight configuration. diff --git a/flutter/lib/src/flutter_error_type_identifier.dart b/flutter/lib/src/flutter_error_type_identifier.dart index e974eea373..97ec628ae0 100644 --- a/flutter/lib/src/flutter_error_type_identifier.dart +++ b/flutter/lib/src/flutter_error_type_identifier.dart @@ -1,17 +1,33 @@ +import 'dart:io'; + import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../sentry_flutter.dart'; -class FlutterErrorIdentifier implements ErrorTypeIdentifier { +class FlutterExceptionTypeIdentifier implements ExceptionTypeIdentifier { @override - String? getTypeName(dynamic error) { - if (error is FlutterError) return 'FlutterError'; - if (error is PlatformException) return 'PlatformException'; - if (error is MissingPluginException) return 'MissingPluginException'; - if (error is AssertionError) return 'AssertionError'; - if (error is NetworkImageLoadException) return 'NetworkImageLoadException'; - if (error is TickerCanceled) return 'TickerCanceled'; + String? identifyType(dynamic throwable) { + if (throwable is FlutterError) return 'FlutterError'; + if (throwable is PlatformException) return 'PlatformException'; + if (throwable is MissingPluginException) return 'MissingPluginException'; + if (throwable is AssertionError) return 'AssertionError'; + if (throwable is NetworkImageLoadException) + return 'NetworkImageLoadException'; + if (throwable is TickerCanceled) return 'TickerCanceled'; + + // dart:io Exceptions + if (!kIsWeb) { + if (throwable is FileSystemException) return 'FileSystemException'; + if (throwable is HttpException) return 'HttpException'; + if (throwable is SocketException) return 'SocketException'; + if (throwable is HandshakeException) return 'HandshakeException'; + if (throwable is CertificateException) return 'CertificateException'; + // not adding TlsException and IOException because it's too generic + } return null; } } + +bool isSubtype() => [] is List; diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 079c4cab11..4204a3e91b 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -137,7 +137,7 @@ mixin SentryFlutter { options.addPerformanceCollector(SpanFrameMetricsCollector(options)); - options.addErrorTypeIdentifier(FlutterErrorIdentifier()); + options.addExceptionTypeIdentifier(FlutterExceptionTypeIdentifier()); _setSdk(options); } From 34c4e114b04bcdf90a40985b24eca6a3ed02ba72 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 16 Jul 2024 16:46:43 +0200 Subject: [PATCH 06/24] Update exception_type_identifier.dart --- dart/lib/src/exception_type_identifier.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/dart/lib/src/exception_type_identifier.dart b/dart/lib/src/exception_type_identifier.dart index a06adda146..7de46c0189 100644 --- a/dart/lib/src/exception_type_identifier.dart +++ b/dart/lib/src/exception_type_identifier.dart @@ -1,9 +1,7 @@ /// An abstract class for identifying the type of Dart errors and exceptions. /// -/// This class provides a contract for implementing error type identification -/// in Dart. It's used in scenarios where error types need to be -/// determined in obfuscated builds, as [runtimeType] is not reliable in -/// such cases. +/// It's used in scenarios where error types need to be determined in obfuscated builds +/// as [runtimeType] is not reliable in such cases. /// /// Implement this class to create custom error type identifiers for errors or exceptions. /// that we do not support out of the box. @@ -12,10 +10,10 @@ /// /// Example: /// ```dart -/// class MyErrorTypeIdentifier implements ErrorTypeIdentifier { +/// class MyExceptionTypeIdentifier implements ExceptionTypeIdentifier { /// @override -/// String? identifyType(dynamic error) { -/// if (error is MyCustomError) return 'MyCustomError'; +/// String? identifyType(dynamic throwable) { +/// if (throwable is MyCustomError) return 'MyCustomError'; /// return null; /// } /// } From df9c16ee8b9f8eb33afbe8474d43cf679b05cf7b Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 17 Jul 2024 15:32:14 +0200 Subject: [PATCH 07/24] Add caching --- .../src/dart_exception_type_identifier.dart | 55 ++++++++++++++----- dart/lib/src/exception_type_identifier.dart | 27 +++++++++ dart/lib/src/sentry.dart | 3 + dart/lib/src/sentry_options.dart | 12 ++-- .../src/flutter_error_type_identifier.dart | 33 ----------- .../flutter_exception_type_identifier.dart | 19 +++++++ flutter/lib/src/sentry_flutter.dart | 7 ++- 7 files changed, 102 insertions(+), 54 deletions(-) delete mode 100644 flutter/lib/src/flutter_error_type_identifier.dart create mode 100644 flutter/lib/src/flutter_exception_type_identifier.dart diff --git a/dart/lib/src/dart_exception_type_identifier.dart b/dart/lib/src/dart_exception_type_identifier.dart index efd0c568e3..0eae23902b 100644 --- a/dart/lib/src/dart_exception_type_identifier.dart +++ b/dart/lib/src/dart_exception_type_identifier.dart @@ -1,23 +1,48 @@ +import 'dart:io'; + +import 'package:http/http.dart'; +import 'dart:async'; + import '../sentry.dart'; class DartExceptionTypeIdentifier implements ExceptionTypeIdentifier { @override - String? identifyType(dynamic error) { - if (error is ArgumentError) return 'ArgumentError'; - if (error is AssertionError) return 'AssertionError'; - if (error is ConcurrentModificationError) + String? identifyType(dynamic throwable) { + // dart:core + if (throwable is ArgumentError) return 'ArgumentError'; + if (throwable is AssertionError) return 'AssertionError'; + if (throwable is ConcurrentModificationError) return 'ConcurrentModificationError'; - if (error is FormatException) return 'FormatException'; - if (error is IndexError) return 'IndexError'; - if (error is NoSuchMethodError) return 'NoSuchMethodError'; - if (error is OutOfMemoryError) return 'OutOfMemoryError'; - if (error is RangeError) return 'RangeError'; - if (error is StackOverflowError) return 'StackOverflowError'; - if (error is StateError) return 'StateError'; - if (error is TypeError) return 'TypeError'; - if (error is UnimplementedError) return 'UnimplementedError'; - if (error is UnsupportedError) return 'UnsupportedError'; - // we purposefully don't include Exception or Error since it's too generic + if (throwable is FormatException) return 'FormatException'; + if (throwable is IndexError) return 'IndexError'; + if (throwable is NoSuchMethodError) return 'NoSuchMethodError'; + if (throwable is OutOfMemoryError) return 'OutOfMemoryError'; + if (throwable is RangeError) return 'RangeError'; + if (throwable is StackOverflowError) return 'StackOverflowError'; + if (throwable is StateError) return 'StateError'; + if (throwable is TypeError) return 'TypeError'; + if (throwable is UnimplementedError) return 'UnimplementedError'; + if (throwable is UnsupportedError) return 'UnsupportedError'; + // not adding Exception or Error because it's too generic + + // dart:async + if (throwable is TimeoutException) return 'TimeoutException'; + if (throwable is AsyncError) return 'FutureTimeout'; + if (throwable is DeferredLoadException) return 'DeferredLoadException'; + // not adding ParallelWaitError because it's not supported in dart 2.17.0 + + // dart:io + if (throwable is FileSystemException) return 'FileSystemException'; + if (throwable is HttpException) return 'HttpException'; + if (throwable is SocketException) return 'SocketException'; + if (throwable is HandshakeException) return 'HandshakeException'; + if (throwable is CertificateException) return 'CertificateException'; + if (throwable is TlsException) return 'TlsException'; + // not adding IOException because it's too generic + + // dart http package + if (throwable is ClientException) return 'ClientException'; + return null; } } diff --git a/dart/lib/src/exception_type_identifier.dart b/dart/lib/src/exception_type_identifier.dart index 7de46c0189..672c57f367 100644 --- a/dart/lib/src/exception_type_identifier.dart +++ b/dart/lib/src/exception_type_identifier.dart @@ -21,3 +21,30 @@ abstract class ExceptionTypeIdentifier { String? identifyType(dynamic throwable); } + +extension CacheableExceptionIdentifier on ExceptionTypeIdentifier { + ExceptionTypeIdentifier withCache() => _CachingExceptionTypeIdentifier(this); +} + +class _CachingExceptionTypeIdentifier implements ExceptionTypeIdentifier { + final ExceptionTypeIdentifier _identifier; + final Map _knownExceptionTypes = {}; + + _CachingExceptionTypeIdentifier(this._identifier); + + @override + String? identifyType(dynamic throwable) { + final runtimeType = throwable.runtimeType; + if (_knownExceptionTypes.containsKey(runtimeType)) { + return _knownExceptionTypes[runtimeType]; + } + + final identifiedType = _identifier.identifyType(throwable); + + if (identifiedType != null) { + _knownExceptionTypes[runtimeType] = identifiedType; + } + + return identifiedType; + } +} diff --git a/dart/lib/src/sentry.dart b/dart/lib/src/sentry.dart index 1873cd6308..79ada88bd6 100644 --- a/dart/lib/src/sentry.dart +++ b/dart/lib/src/sentry.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'dart_exception_type_identifier.dart'; import 'metrics/metrics_api.dart'; import 'run_zoned_guarded_integration.dart'; import 'event_processor/enricher/enricher_event_processor.dart'; @@ -85,6 +86,8 @@ class Sentry { options.addEventProcessor(EnricherEventProcessor(options)); options.addEventProcessor(ExceptionEventProcessor(options)); options.addEventProcessor(DeduplicationEventProcessor(options)); + + options.addExceptionTypeIdentifier(DartExceptionTypeIdentifier()); } /// This method reads available environment variables and uses them diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index f0acc5be97..96b1156e25 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -437,16 +437,20 @@ class SentryOptions { /// Settings this to `false` will set the `level` to [SentryLevel.error]. bool markAutomaticallyCollectedErrorsAsFatal = true; - final List _exceptionTypeIdentifiers = [ - DartExceptionTypeIdentifier(), - ]; + final List _exceptionTypeIdentifiers = []; List get exceptionTypeIdentifiers => _exceptionTypeIdentifiers; + void addExceptionTypeIdentifierByIndex( + int index, ExceptionTypeIdentifier exceptionTypeIdentifier) { + _exceptionTypeIdentifiers.insert( + index, exceptionTypeIdentifier.withCache()); + } + void addExceptionTypeIdentifier( ExceptionTypeIdentifier exceptionTypeIdentifier) { - _exceptionTypeIdentifiers.insert(0, exceptionTypeIdentifier); + _exceptionTypeIdentifiers.add(exceptionTypeIdentifier.withCache()); } /// The Spotlight configuration. diff --git a/flutter/lib/src/flutter_error_type_identifier.dart b/flutter/lib/src/flutter_error_type_identifier.dart deleted file mode 100644 index 97ec628ae0..0000000000 --- a/flutter/lib/src/flutter_error_type_identifier.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -import '../sentry_flutter.dart'; - -class FlutterExceptionTypeIdentifier implements ExceptionTypeIdentifier { - @override - String? identifyType(dynamic throwable) { - if (throwable is FlutterError) return 'FlutterError'; - if (throwable is PlatformException) return 'PlatformException'; - if (throwable is MissingPluginException) return 'MissingPluginException'; - if (throwable is AssertionError) return 'AssertionError'; - if (throwable is NetworkImageLoadException) - return 'NetworkImageLoadException'; - if (throwable is TickerCanceled) return 'TickerCanceled'; - - // dart:io Exceptions - if (!kIsWeb) { - if (throwable is FileSystemException) return 'FileSystemException'; - if (throwable is HttpException) return 'HttpException'; - if (throwable is SocketException) return 'SocketException'; - if (throwable is HandshakeException) return 'HandshakeException'; - if (throwable is CertificateException) return 'CertificateException'; - // not adding TlsException and IOException because it's too generic - } - return null; - } -} - -bool isSubtype() => [] is List; diff --git a/flutter/lib/src/flutter_exception_type_identifier.dart b/flutter/lib/src/flutter_exception_type_identifier.dart new file mode 100644 index 0000000000..d3a574833c --- /dev/null +++ b/flutter/lib/src/flutter_exception_type_identifier.dart @@ -0,0 +1,19 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; + +import '../sentry_flutter.dart'; + +class FlutterExceptionTypeIdentifier implements ExceptionTypeIdentifier { + @override + String? identifyType(dynamic throwable) { + // FlutterError check should run before AssertionError check because + // it's a subclass of AssertionError + if (throwable is FlutterError) return 'FlutterError'; + if (throwable is PlatformException) return 'PlatformException'; + if (throwable is MissingPluginException) return 'MissingPluginException'; + if (throwable is NetworkImageLoadException) + return 'NetworkImageLoadException'; + if (throwable is TickerCanceled) return 'TickerCanceled'; + return null; + } +} diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 4204a3e91b..d7a3c118ec 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -11,7 +11,7 @@ import 'event_processor/flutter_exception_event_processor.dart'; import 'event_processor/platform_exception_event_processor.dart'; import 'event_processor/widget_event_processor.dart'; import 'file_system_transport.dart'; -import 'flutter_error_type_identifier.dart'; +import 'flutter_exception_type_identifier.dart'; import 'frame_callback_handler.dart'; import 'integrations/connectivity/connectivity_integration.dart'; import 'integrations/integrations.dart'; @@ -137,7 +137,10 @@ mixin SentryFlutter { options.addPerformanceCollector(SpanFrameMetricsCollector(options)); - options.addExceptionTypeIdentifier(FlutterExceptionTypeIdentifier()); + // Insert it before the Dart Exceptions that are set in Sentry.init + // so we can identify Flutter exceptions first. + options.addExceptionTypeIdentifierByIndex( + 0, FlutterExceptionTypeIdentifier()); _setSdk(options); } From ff04d22608eb7c25e435e044d4d4b2c3176c3e8c Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 18 Jul 2024 16:20:55 +0200 Subject: [PATCH 08/24] Update --- dart/lib/src/exception_type_identifier.dart | 11 +- dart/lib/src/sentry.dart | 2 +- dart/lib/src/sentry_exception_factory.dart | 13 +- dart/lib/src/sentry_options.dart | 17 +- .../client_reports/client_report_test.dart | 7 + dart/test/exception_identifier_test.dart | 155 ++++++++++++++++++ dart/test/mocks.dart | 1 + dart/test/mocks.mocks.dart | 15 +- 8 files changed, 207 insertions(+), 14 deletions(-) create mode 100644 dart/test/exception_identifier_test.dart diff --git a/dart/lib/src/exception_type_identifier.dart b/dart/lib/src/exception_type_identifier.dart index 672c57f367..5ffce7bb40 100644 --- a/dart/lib/src/exception_type_identifier.dart +++ b/dart/lib/src/exception_type_identifier.dart @@ -1,3 +1,5 @@ +import 'package:meta/meta.dart'; + /// An abstract class for identifying the type of Dart errors and exceptions. /// /// It's used in scenarios where error types need to be determined in obfuscated builds @@ -6,7 +8,7 @@ /// Implement this class to create custom error type identifiers for errors or exceptions. /// that we do not support out of the box. /// -/// Add the implementation using [SentryOptions.addExceptionTypeIdentifier]. +/// Add the implementation using [SentryOptions.prependExceptionTypeIdentifier]. /// /// Example: /// ```dart @@ -23,14 +25,15 @@ abstract class ExceptionTypeIdentifier { } extension CacheableExceptionIdentifier on ExceptionTypeIdentifier { - ExceptionTypeIdentifier withCache() => _CachingExceptionTypeIdentifier(this); + ExceptionTypeIdentifier withCache() => CachingExceptionTypeIdentifier(this); } -class _CachingExceptionTypeIdentifier implements ExceptionTypeIdentifier { +@visibleForTesting +class CachingExceptionTypeIdentifier implements ExceptionTypeIdentifier { final ExceptionTypeIdentifier _identifier; final Map _knownExceptionTypes = {}; - _CachingExceptionTypeIdentifier(this._identifier); + CachingExceptionTypeIdentifier(this._identifier); @override String? identifyType(dynamic throwable) { diff --git a/dart/lib/src/sentry.dart b/dart/lib/src/sentry.dart index 79ada88bd6..a3ac51e818 100644 --- a/dart/lib/src/sentry.dart +++ b/dart/lib/src/sentry.dart @@ -87,7 +87,7 @@ class Sentry { options.addEventProcessor(ExceptionEventProcessor(options)); options.addEventProcessor(DeduplicationEventProcessor(options)); - options.addExceptionTypeIdentifier(DartExceptionTypeIdentifier()); + options.prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); } /// This method reads available environment variables and uses them diff --git a/dart/lib/src/sentry_exception_factory.dart b/dart/lib/src/sentry_exception_factory.dart index dceeb8753e..9ee2148c14 100644 --- a/dart/lib/src/sentry_exception_factory.dart +++ b/dart/lib/src/sentry_exception_factory.dart @@ -62,11 +62,14 @@ class SentryExceptionFactory { final value = throwableString.replaceAll(stackTraceString, '').trim(); String errorTypeName = throwable.runtimeType.toString(); - for (final errorTypeIdentifier in _options.exceptionTypeIdentifiers) { - final identifiedErrorType = errorTypeIdentifier.identifyType(throwable); - if (identifiedErrorType != null) { - errorTypeName = identifiedErrorType; - break; + + if (_options.enableExceptionTypeIdentification) { + for (final errorTypeIdentifier in _options.exceptionTypeIdentifiers) { + final identifiedErrorType = errorTypeIdentifier.identifyType(throwable); + if (identifiedErrorType != null) { + errorTypeName = identifiedErrorType; + break; + } } } diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 96b1156e25..5a5e9197e7 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -437,10 +437,19 @@ class SentryOptions { /// Settings this to `false` will set the `level` to [SentryLevel.error]. bool markAutomaticallyCollectedErrorsAsFatal = true; + /// Enables identification of exception types in obfuscated builds. + /// When true, the SDK will attempt to identify common exception types + /// to improve readability of obfuscated issue titles. + /// + /// If you already have issues with obfuscated issue titles this will change grouping. + /// + /// Default: `true` + bool enableExceptionTypeIdentification = true; + final List _exceptionTypeIdentifiers = []; List get exceptionTypeIdentifiers => - _exceptionTypeIdentifiers; + List.unmodifiable(_exceptionTypeIdentifiers); void addExceptionTypeIdentifierByIndex( int index, ExceptionTypeIdentifier exceptionTypeIdentifier) { @@ -448,9 +457,11 @@ class SentryOptions { index, exceptionTypeIdentifier.withCache()); } - void addExceptionTypeIdentifier( + /// Adds an exception type identifier to the beginning of the list. + /// This ensures it is processed first and takes precedence over existing identifiers. + void prependExceptionTypeIdentifier( ExceptionTypeIdentifier exceptionTypeIdentifier) { - _exceptionTypeIdentifiers.add(exceptionTypeIdentifier.withCache()); + addExceptionTypeIdentifierByIndex(0, exceptionTypeIdentifier); } /// The Spotlight configuration. diff --git a/dart/test/client_reports/client_report_test.dart b/dart/test/client_reports/client_report_test.dart index 00f5736cc2..aa096378da 100644 --- a/dart/test/client_reports/client_report_test.dart +++ b/dart/test/client_reports/client_report_test.dart @@ -6,6 +6,12 @@ import 'package:sentry/src/transport/data_category.dart'; import 'package:test/test.dart'; import 'package:sentry/src/utils.dart'; +class CustomException implements Exception { + message() { + return 'This is a custom exception'; + } +} + void main() { group('json', () { late Fixture fixture; @@ -15,6 +21,7 @@ void main() { }); test('toJson', () { + print(CustomException().runtimeType.toString()); final sut = fixture.getSut(); final json = sut.toJson(); diff --git a/dart/test/exception_identifier_test.dart b/dart/test/exception_identifier_test.dart new file mode 100644 index 0000000000..fdf5655e43 --- /dev/null +++ b/dart/test/exception_identifier_test.dart @@ -0,0 +1,155 @@ +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/dart_exception_type_identifier.dart'; +import 'package:sentry/src/sentry_exception_factory.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'mocks.mocks.dart'; +import 'mocks/mock_transport.dart'; +import 'sentry_client_test.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + group('ExceptionTypeIdentifiers', () { + test('first in the list will be processed first', () { + fixture.options + .prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); + fixture.options + .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); + + final factory = SentryExceptionFactory(fixture.options); + final sentryException = factory.getSentryException(ObfuscatedException()); + + expect(sentryException.type, equals('ObfuscatedException')); + }); + }); + + group('CachingExceptionTypeIdentifier', () { + late MockExceptionTypeIdentifier mockIdentifier; + late CachingExceptionTypeIdentifier cachingIdentifier; + + setUp(() { + mockIdentifier = MockExceptionTypeIdentifier(); + cachingIdentifier = CachingExceptionTypeIdentifier(mockIdentifier); + }); + + test('should return cached result for known types', () { + final exception = Exception('Test'); + when(mockIdentifier.identifyType(exception)).thenReturn('TestException'); + + expect( + cachingIdentifier.identifyType(exception), equals('TestException')); + expect( + cachingIdentifier.identifyType(exception), equals('TestException')); + expect( + cachingIdentifier.identifyType(exception), equals('TestException')); + + verify(mockIdentifier.identifyType(exception)).called(1); + }); + + test('should not cache unknown types', () { + final exception = ObfuscatedException(); + + when(mockIdentifier.identifyType(exception)).thenReturn(null); + + expect(cachingIdentifier.identifyType(exception), isNull); + expect(cachingIdentifier.identifyType(exception), isNull); + expect(cachingIdentifier.identifyType(exception), isNull); + + verify(mockIdentifier.identifyType(exception)).called(3); + }); + + test('should return null for unknown exception type', () { + final exception = Exception('Unknown'); + when(mockIdentifier.identifyType(exception)).thenReturn(null); + + expect(cachingIdentifier.identifyType(exception), isNull); + }); + + test('should handle different exception types separately', () { + final exception1 = Exception('Test1'); + final exception2 = FormatException('Test2'); + + when(mockIdentifier.identifyType(exception1)).thenReturn('Exception'); + when(mockIdentifier.identifyType(exception2)) + .thenReturn('FormatException'); + + expect(cachingIdentifier.identifyType(exception1), equals('Exception')); + expect(cachingIdentifier.identifyType(exception2), + equals('FormatException')); + + // Call again to test caching + expect(cachingIdentifier.identifyType(exception1), equals('Exception')); + expect(cachingIdentifier.identifyType(exception2), + equals('FormatException')); + + verify(mockIdentifier.identifyType(exception1)).called(1); + verify(mockIdentifier.identifyType(exception2)).called(1); + }); + }); + + group('Integration test', () { + setUp(() { + fixture.options.transport = MockTransport(); + }); + + test( + 'should capture CustomException as exception type with custom identifier', + () async { + fixture.options + .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); + + final client = SentryClient(fixture.options); + + await client.captureException(ObfuscatedException()); + + final transport = fixture.options.transport as MockTransport; + final capturedEnvelope = transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect( + capturedEvent.exceptions!.first.type, equals('ObfuscatedException')); + }); + + test( + 'should capture PlaceHolderException as exception type without custom identifier', + () async { + final client = SentryClient(fixture.options); + + await client.captureException(ObfuscatedException()); + + final transport = fixture.options.transport as MockTransport; + final capturedEnvelope = transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect( + capturedEvent.exceptions!.first.type, equals('PlaceHolderException')); + }); + }); +} + +class Fixture { + SentryOptions options = SentryOptions(dsn: fakeDsn); +} + +// We use this PlaceHolder exception to mimic an obfuscated runtimeType +class PlaceHolderException implements Exception {} + +class ObfuscatedException implements Exception { + @override + Type get runtimeType => PlaceHolderException; +} + +class ObfuscatedExceptionIdentifier implements ExceptionTypeIdentifier { + @override + String? identifyType(dynamic throwable) { + if (throwable is ObfuscatedException) return 'ObfuscatedException'; + return null; + } +} diff --git a/dart/test/mocks.dart b/dart/test/mocks.dart index b05843be8e..fc68270295 100644 --- a/dart/test/mocks.dart +++ b/dart/test/mocks.dart @@ -207,5 +207,6 @@ class MockRateLimiter implements RateLimiter { SentryProfilerFactory, SentryProfiler, SentryProfileInfo, + ExceptionTypeIdentifier, ]) void main() {} diff --git a/dart/test/mocks.mocks.dart b/dart/test/mocks.mocks.dart index 5f2556400e..58133b794b 100644 --- a/dart/test/mocks.mocks.dart +++ b/dart/test/mocks.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.2 from annotations +// Mocks generated by Mockito 5.4.4 from annotations // in sentry/test/mocks.dart. // Do not manually edit this file. @@ -13,6 +13,8 @@ import 'package:sentry/src/profiling.dart' as _i3; // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors @@ -66,6 +68,7 @@ class MockSentryProfiler extends _i1.Mock implements _i3.SentryProfiler { ), returnValue: _i4.Future<_i3.SentryProfileInfo?>.value(), ) as _i4.Future<_i3.SentryProfileInfo?>); + @override void dispose() => super.noSuchMethod( Invocation.method( @@ -99,3 +102,13 @@ class MockSentryProfileInfo extends _i1.Mock implements _i3.SentryProfileInfo { ), ) as _i2.SentryEnvelopeItem); } + +/// A class which mocks [ExceptionTypeIdentifier]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockExceptionTypeIdentifier extends _i1.Mock + implements _i2.ExceptionTypeIdentifier { + MockExceptionTypeIdentifier() { + _i1.throwOnMissingStub(this); + } +} From 4fb9c8606bc2536f11d315aa702d03a18d09d7dd Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 18 Jul 2024 16:44:51 +0200 Subject: [PATCH 09/24] split up dart:io and dart:html exceptions --- .../lib/src/dart_exception_type_identifier.dart | 17 +++++------------ .../src/dart_exception_type_identifier_io.dart | 14 ++++++++++++++ .../src/dart_exception_type_identifier_web.dart | 8 ++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 dart/lib/src/dart_exception_type_identifier_io.dart create mode 100644 dart/lib/src/dart_exception_type_identifier_web.dart diff --git a/dart/lib/src/dart_exception_type_identifier.dart b/dart/lib/src/dart_exception_type_identifier.dart index 0eae23902b..5f1c4636b0 100644 --- a/dart/lib/src/dart_exception_type_identifier.dart +++ b/dart/lib/src/dart_exception_type_identifier.dart @@ -1,10 +1,11 @@ -import 'dart:io'; - import 'package:http/http.dart'; import 'dart:async'; import '../sentry.dart'; +import 'dart_exception_type_identifier_io.dart' + if (dart.library.html) 'dart_exception_type_identifier_web.dart'; + class DartExceptionTypeIdentifier implements ExceptionTypeIdentifier { @override String? identifyType(dynamic throwable) { @@ -31,18 +32,10 @@ class DartExceptionTypeIdentifier implements ExceptionTypeIdentifier { if (throwable is DeferredLoadException) return 'DeferredLoadException'; // not adding ParallelWaitError because it's not supported in dart 2.17.0 - // dart:io - if (throwable is FileSystemException) return 'FileSystemException'; - if (throwable is HttpException) return 'HttpException'; - if (throwable is SocketException) return 'SocketException'; - if (throwable is HandshakeException) return 'HandshakeException'; - if (throwable is CertificateException) return 'CertificateException'; - if (throwable is TlsException) return 'TlsException'; - // not adding IOException because it's too generic - // dart http package if (throwable is ClientException) return 'ClientException'; - return null; + // platform specific exceptions + return identifyPlatformSpecificException(throwable); } } diff --git a/dart/lib/src/dart_exception_type_identifier_io.dart b/dart/lib/src/dart_exception_type_identifier_io.dart new file mode 100644 index 0000000000..1945663a01 --- /dev/null +++ b/dart/lib/src/dart_exception_type_identifier_io.dart @@ -0,0 +1,14 @@ +import 'dart:io'; + +import 'package:meta/meta.dart'; + +@internal +String? identifyPlatformSpecificException(dynamic throwable) { + if (throwable is FileSystemException) return 'FileSystemException'; + if (throwable is HttpException) return 'HttpException'; + if (throwable is SocketException) return 'SocketException'; + if (throwable is HandshakeException) return 'HandshakeException'; + if (throwable is CertificateException) return 'CertificateException'; + if (throwable is TlsException) return 'TlsException'; + return null; +} diff --git a/dart/lib/src/dart_exception_type_identifier_web.dart b/dart/lib/src/dart_exception_type_identifier_web.dart new file mode 100644 index 0000000000..e581742536 --- /dev/null +++ b/dart/lib/src/dart_exception_type_identifier_web.dart @@ -0,0 +1,8 @@ +import 'dart:html'; +import 'package:meta/meta.dart'; + +@internal +String? identifyPlatformSpecificException(dynamic throwable) { + if (throwable is NullWindowException) return 'NullWindowException'; + return null; +} From 04dace5cacc1a142568ca9aeaef7f8c41ddb77b3 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 18 Jul 2024 16:50:18 +0200 Subject: [PATCH 10/24] fix analyze --- dart/lib/src/dart_exception_type_identifier.dart | 3 ++- dart/lib/src/sentry_options.dart | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dart/lib/src/dart_exception_type_identifier.dart b/dart/lib/src/dart_exception_type_identifier.dart index 5f1c4636b0..aa5c877b60 100644 --- a/dart/lib/src/dart_exception_type_identifier.dart +++ b/dart/lib/src/dart_exception_type_identifier.dart @@ -12,8 +12,9 @@ class DartExceptionTypeIdentifier implements ExceptionTypeIdentifier { // dart:core if (throwable is ArgumentError) return 'ArgumentError'; if (throwable is AssertionError) return 'AssertionError'; - if (throwable is ConcurrentModificationError) + if (throwable is ConcurrentModificationError) { return 'ConcurrentModificationError'; + } if (throwable is FormatException) return 'FormatException'; if (throwable is IndexError) return 'IndexError'; if (throwable is NoSuchMethodError) return 'NoSuchMethodError'; diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 5a5e9197e7..6c77acec4a 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -7,7 +7,6 @@ import 'package:meta/meta.dart'; import '../sentry.dart'; import 'client_reports/client_report_recorder.dart'; import 'client_reports/noop_client_report_recorder.dart'; -import 'dart_exception_type_identifier.dart'; import 'diagnostic_logger.dart'; import 'environment/environment_variables.dart'; import 'noop_client.dart'; From 40fba24bc6f98e96c10d1133f876c91be9ac0549 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 18 Jul 2024 17:00:08 +0200 Subject: [PATCH 11/24] Update CHANGELOG --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1da39b4b1..4bc156a8f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ ### Improvements +- Add error type identifier to improve obfuscated Flutter issue titles ([#2170](https://github.com/getsentry/sentry-dart/pull/2170)) + - Example: transforms issue titles from `GA` to `FlutterError` or `minified:nE` to `FlutterError` + - This is enabled automatically and will change grouping if you already have issues with obfuscated titles + - You can add your custom exception identifier if there are exceptions that we do not identify out of the box + - If you want to disable this feature, set `enableExceptionTypeIdentification` to `false` in your Sentry options +```dart +// How to add your own custom exception identifier +class MyCustomExceptionIdentifier implements ExceptionIdentifier { + @override + String identify(Exception exception) { + if (exception is MyCustomException) { + return 'MyCustomException'; + } + if (exception is MyOtherCustomException) { + return 'MyOtherCustomException'; + } + return null; + } +} + +SentryFlutter.init((options) => + options..prependExceptionTypeIdentifier(MyCustomExceptionIdentifier())); +``` - Capture meaningful stack traces when unhandled errors have empty or missing stack traces ([#2152](https://github.com/getsentry/sentry-dart/pull/2152)) - This will affect grouping for unhandled errors that have empty or missing stack traces. From 51e25c03251e90b34e9e7d2753cab26fe96864a2 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 18 Jul 2024 17:07:25 +0200 Subject: [PATCH 12/24] update --- dart/lib/src/dart_exception_type_identifier_web.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/dart/lib/src/dart_exception_type_identifier_web.dart b/dart/lib/src/dart_exception_type_identifier_web.dart index e581742536..088ce9556e 100644 --- a/dart/lib/src/dart_exception_type_identifier_web.dart +++ b/dart/lib/src/dart_exception_type_identifier_web.dart @@ -1,8 +1,6 @@ -import 'dart:html'; import 'package:meta/meta.dart'; @internal String? identifyPlatformSpecificException(dynamic throwable) { - if (throwable is NullWindowException) return 'NullWindowException'; return null; } From bc25f4863c3fcdba8aeef300ce208d8696e2c26e Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 19 Jul 2024 11:05:19 +0200 Subject: [PATCH 13/24] Add more tests --- dart/lib/src/exception_type_identifier.dart | 2 ++ dart/test/exception_identifier_test.dart | 32 ++++++++++++++++++- dart/test/sentry_options_test.dart | 6 ++++ dart/test/sentry_test.dart | 23 +++++++++++++ .../flutter_exception_type_identifier.dart | 3 +- flutter/lib/src/sentry_flutter.dart | 10 +++--- flutter/test/sentry_flutter_test.dart | 32 +++++++++++++++++++ 7 files changed, 101 insertions(+), 7 deletions(-) diff --git a/dart/lib/src/exception_type_identifier.dart b/dart/lib/src/exception_type_identifier.dart index 5ffce7bb40..4cde5dc564 100644 --- a/dart/lib/src/exception_type_identifier.dart +++ b/dart/lib/src/exception_type_identifier.dart @@ -30,6 +30,8 @@ extension CacheableExceptionIdentifier on ExceptionTypeIdentifier { @visibleForTesting class CachingExceptionTypeIdentifier implements ExceptionTypeIdentifier { + @visibleForTesting + ExceptionTypeIdentifier get identifier => _identifier; final ExceptionTypeIdentifier _identifier; final Map _knownExceptionTypes = {}; diff --git a/dart/test/exception_identifier_test.dart b/dart/test/exception_identifier_test.dart index fdf5655e43..b3014203dd 100644 --- a/dart/test/exception_identifier_test.dart +++ b/dart/test/exception_identifier_test.dart @@ -17,7 +17,7 @@ void main() { }); group('ExceptionTypeIdentifiers', () { - test('first in the list will be processed first', () { + test('should be processed based on order in the list', () { fixture.options .prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); fixture.options @@ -28,6 +28,36 @@ void main() { expect(sentryException.type, equals('ObfuscatedException')); }); + + test('should return null if exception is not identified', () { + final identifier = DartExceptionTypeIdentifier(); + expect(identifier.identifyType(ObfuscatedException()), isNull); + }); + }); + + group('SentryExceptionFactory', () { + test('should process identifiers based on order in the list', () { + fixture.options + .prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); + fixture.options + .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); + + final factory = SentryExceptionFactory(fixture.options); + final sentryException = factory.getSentryException(ObfuscatedException()); + + expect(sentryException.type, equals('ObfuscatedException')); + }); + + test('should use runtime type when identification is disabled', () { + fixture.options.enableExceptionTypeIdentification = false; + fixture.options + .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); + + final factory = SentryExceptionFactory(fixture.options); + final sentryException = factory.getSentryException(ObfuscatedException()); + + expect(sentryException.type, equals('PlaceHolderException')); + }); }); group('CachingExceptionTypeIdentifier', () { diff --git a/dart/test/sentry_options_test.dart b/dart/test/sentry_options_test.dart index 273366e442..3097597a92 100644 --- a/dart/test/sentry_options_test.dart +++ b/dart/test/sentry_options_test.dart @@ -140,6 +140,12 @@ void main() { expect(options.enableMetrics, false); }); + test('enableExceptionTypeIdentification is enabled by default', () { + final options = SentryOptions(dsn: fakeDsn); + + expect(options.enableExceptionTypeIdentification, true); + }); + test('default tags for metrics are enabled by default', () { final options = SentryOptions(dsn: fakeDsn); options.enableMetrics = true; diff --git a/dart/test/sentry_test.dart b/dart/test/sentry_test.dart index 0a8dd5db27..1b363b99a0 100644 --- a/dart/test/sentry_test.dart +++ b/dart/test/sentry_test.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:sentry/sentry.dart'; +import 'package:sentry/src/dart_exception_type_identifier.dart'; import 'package:sentry/src/event_processor/deduplication_event_processor.dart'; import 'package:test/test.dart'; @@ -318,6 +319,28 @@ void main() { expect(completed, true); }); + + test('should add DartExceptionTypeIdentifier by default', () async { + final options = SentryOptions(dsn: fakeDsn)..automatedTestMode = true; + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + }, + ); + + expect(options.exceptionTypeIdentifiers.length, 1); + final cachingIdentifier = options.exceptionTypeIdentifiers.first + as CachingExceptionTypeIdentifier; + expect( + cachingIdentifier, + isA().having( + (c) => c.identifier, + 'wrapped identifier', + isA(), + ), + ); + }); }); test('should complete when appRunner is not called in runZonedGuarded', diff --git a/flutter/lib/src/flutter_exception_type_identifier.dart b/flutter/lib/src/flutter_exception_type_identifier.dart index d3a574833c..6a6e60ac75 100644 --- a/flutter/lib/src/flutter_exception_type_identifier.dart +++ b/flutter/lib/src/flutter_exception_type_identifier.dart @@ -11,8 +11,9 @@ class FlutterExceptionTypeIdentifier implements ExceptionTypeIdentifier { if (throwable is FlutterError) return 'FlutterError'; if (throwable is PlatformException) return 'PlatformException'; if (throwable is MissingPluginException) return 'MissingPluginException'; - if (throwable is NetworkImageLoadException) + if (throwable is NetworkImageLoadException) { return 'NetworkImageLoadException'; + } if (throwable is TickerCanceled) return 'TickerCanceled'; return null; } diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index d7a3c118ec..6044f1b3c1 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -113,6 +113,11 @@ mixin SentryFlutter { // ignore: invalid_use_of_internal_member SentryNativeProfilerFactory.attachTo(Sentry.currentHub, _native!); } + + // Insert it at the start of the list, before the Dart Exceptions that are set in Sentry.init + // so we can identify Flutter exceptions first. + flutterOptions + .prependExceptionTypeIdentifier(FlutterExceptionTypeIdentifier()); } static Future _initDefaultValues(SentryFlutterOptions options) async { @@ -137,11 +142,6 @@ mixin SentryFlutter { options.addPerformanceCollector(SpanFrameMetricsCollector(options)); - // Insert it before the Dart Exceptions that are set in Sentry.init - // so we can identify Flutter exceptions first. - options.addExceptionTypeIdentifierByIndex( - 0, FlutterExceptionTypeIdentifier()); - _setSdk(options); } diff --git a/flutter/test/sentry_flutter_test.dart b/flutter/test/sentry_flutter_test.dart index 3987cdfb7e..303b42ee7f 100644 --- a/flutter/test/sentry_flutter_test.dart +++ b/flutter/test/sentry_flutter_test.dart @@ -4,7 +4,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry/src/platform/platform.dart'; +import 'package:sentry/src/dart_exception_type_identifier.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/flutter_exception_type_identifier.dart'; import 'package:sentry_flutter/src/integrations/connectivity/connectivity_integration.dart'; import 'package:sentry_flutter/src/integrations/integrations.dart'; import 'package:sentry_flutter/src/integrations/screenshot_integration.dart'; @@ -659,6 +661,36 @@ void main() { // This should complete without throwing an error await expectLater(SentryFlutter.pauseAppHangTracking(), completes); }); + + test( + 'should add DartExceptionTypeIdentifier and FlutterExceptionTypeIdentifier by default', + () async { + await SentryFlutter.init((options) { + options.dsn = fakeDsn; + options.automatedTestMode = true; + }); + + final options = HubAdapter().options; + + expect(options.exceptionTypeIdentifiers.length, 2); + // Flutter identifier should be first as it's more specific + expect( + options.exceptionTypeIdentifiers.first, + isA().having( + (c) => c.identifier, + 'wrapped identifier', + isA(), + ), + ); + expect( + options.exceptionTypeIdentifiers[1], + isA().having( + (c) => c.identifier, + 'wrapped identifier', + isA(), + ), + ); + }); } void appRunner() {} From fe9144f9fd33b218e47e248f9fe7d4466c957127 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 19 Jul 2024 11:13:18 +0200 Subject: [PATCH 14/24] Update docs --- dart/lib/src/exception_type_identifier.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dart/lib/src/exception_type_identifier.dart b/dart/lib/src/exception_type_identifier.dart index 4cde5dc564..7dae9db841 100644 --- a/dart/lib/src/exception_type_identifier.dart +++ b/dart/lib/src/exception_type_identifier.dart @@ -8,8 +8,6 @@ import 'package:meta/meta.dart'; /// Implement this class to create custom error type identifiers for errors or exceptions. /// that we do not support out of the box. /// -/// Add the implementation using [SentryOptions.prependExceptionTypeIdentifier]. -/// /// Example: /// ```dart /// class MyExceptionTypeIdentifier implements ExceptionTypeIdentifier { @@ -33,6 +31,7 @@ class CachingExceptionTypeIdentifier implements ExceptionTypeIdentifier { @visibleForTesting ExceptionTypeIdentifier get identifier => _identifier; final ExceptionTypeIdentifier _identifier; + final Map _knownExceptionTypes = {}; CachingExceptionTypeIdentifier(this._identifier); From c22636fb6fb17a8e8924dacc640b00424b41bc08 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 19 Jul 2024 11:13:54 +0200 Subject: [PATCH 15/24] Update options docs --- dart/lib/src/sentry_options.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 6c77acec4a..2c84d421c9 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -440,7 +440,7 @@ class SentryOptions { /// When true, the SDK will attempt to identify common exception types /// to improve readability of obfuscated issue titles. /// - /// If you already have issues with obfuscated issue titles this will change grouping. + /// If you already have events with obfuscated issue titles this will change grouping. /// /// Default: `true` bool enableExceptionTypeIdentification = true; From f9da0a42fdc0c3400f9dd171f7b308bbe66000a3 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 19 Jul 2024 11:21:42 +0200 Subject: [PATCH 16/24] remove print --- dart/test/client_reports/client_report_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/dart/test/client_reports/client_report_test.dart b/dart/test/client_reports/client_report_test.dart index aa096378da..09c0beb07c 100644 --- a/dart/test/client_reports/client_report_test.dart +++ b/dart/test/client_reports/client_report_test.dart @@ -21,7 +21,6 @@ void main() { }); test('toJson', () { - print(CustomException().runtimeType.toString()); final sut = fixture.getSut(); final json = sut.toJson(); From 0305205d12fc4460422701292427db4bc75cf099 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 19 Jul 2024 11:23:06 +0200 Subject: [PATCH 17/24] remove CustomException --- dart/test/client_reports/client_report_test.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dart/test/client_reports/client_report_test.dart b/dart/test/client_reports/client_report_test.dart index 09c0beb07c..00f5736cc2 100644 --- a/dart/test/client_reports/client_report_test.dart +++ b/dart/test/client_reports/client_report_test.dart @@ -6,12 +6,6 @@ import 'package:sentry/src/transport/data_category.dart'; import 'package:test/test.dart'; import 'package:sentry/src/utils.dart'; -class CustomException implements Exception { - message() { - return 'This is a custom exception'; - } -} - void main() { group('json', () { late Fixture fixture; From facc3ba22c7034119c0bcd704b6bf8dd2336f108 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 24 Jul 2024 10:22:47 +0200 Subject: [PATCH 18/24] import with show --- dart/lib/src/dart_exception_type_identifier.dart | 5 ++--- flutter/lib/src/flutter_exception_type_identifier.dart | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dart/lib/src/dart_exception_type_identifier.dart b/dart/lib/src/dart_exception_type_identifier.dart index aa5c877b60..9be98c608a 100644 --- a/dart/lib/src/dart_exception_type_identifier.dart +++ b/dart/lib/src/dart_exception_type_identifier.dart @@ -1,6 +1,5 @@ -import 'package:http/http.dart'; -import 'dart:async'; - +import 'package:http/http.dart' show ClientException; +import 'dart:async' show TimeoutException, AsyncError, DeferredLoadException; import '../sentry.dart'; import 'dart_exception_type_identifier_io.dart' diff --git a/flutter/lib/src/flutter_exception_type_identifier.dart b/flutter/lib/src/flutter_exception_type_identifier.dart index 6a6e60ac75..755f09e544 100644 --- a/flutter/lib/src/flutter_exception_type_identifier.dart +++ b/flutter/lib/src/flutter_exception_type_identifier.dart @@ -1,5 +1,7 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter/cupertino.dart' + show FlutterError, NetworkImageLoadException, TickerCanceled; +import 'package:flutter/services.dart' + show PlatformException, MissingPluginException; import '../sentry_flutter.dart'; From c31fee1140d73da82bffb5e9701c8e5751d307f2 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 24 Jul 2024 12:28:38 +0200 Subject: [PATCH 19/24] try fix test --- flutter/test/sentry_flutter_test.dart | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/flutter/test/sentry_flutter_test.dart b/flutter/test/sentry_flutter_test.dart index 303b42ee7f..efb38e4ace 100644 --- a/flutter/test/sentry_flutter_test.dart +++ b/flutter/test/sentry_flutter_test.dart @@ -665,17 +665,20 @@ void main() { test( 'should add DartExceptionTypeIdentifier and FlutterExceptionTypeIdentifier by default', () async { - await SentryFlutter.init((options) { - options.dsn = fakeDsn; - options.automatedTestMode = true; - }); - - final options = HubAdapter().options; + SentryOptions? actualOptions = null; + await SentryFlutter.init( + (options) { + options.dsn = fakeDsn; + options.automatedTestMode = true; + actualOptions = options; + }, + appRunner: appRunner, + ); - expect(options.exceptionTypeIdentifiers.length, 2); + expect(actualOptions!.exceptionTypeIdentifiers.length, 2); // Flutter identifier should be first as it's more specific expect( - options.exceptionTypeIdentifiers.first, + actualOptions!.exceptionTypeIdentifiers.first, isA().having( (c) => c.identifier, 'wrapped identifier', @@ -683,13 +686,15 @@ void main() { ), ); expect( - options.exceptionTypeIdentifiers[1], + actualOptions!.exceptionTypeIdentifiers[1], isA().having( (c) => c.identifier, 'wrapped identifier', isA(), ), ); + + await Sentry.close(); }); } From 5eeeef9fa68d75fd5c7e48b28971f1a12bb46a34 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 25 Jul 2024 11:54:09 +0200 Subject: [PATCH 20/24] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bc156a8f8..db0a0a9bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,8 @@ - Add error type identifier to improve obfuscated Flutter issue titles ([#2170](https://github.com/getsentry/sentry-dart/pull/2170)) - Example: transforms issue titles from `GA` to `FlutterError` or `minified:nE` to `FlutterError` - This is enabled automatically and will change grouping if you already have issues with obfuscated titles - - You can add your custom exception identifier if there are exceptions that we do not identify out of the box - If you want to disable this feature, set `enableExceptionTypeIdentification` to `false` in your Sentry options + - You can add your custom exception identifier if there are exceptions that we do not identify out of the box ```dart // How to add your own custom exception identifier class MyCustomExceptionIdentifier implements ExceptionIdentifier { From cee56f398516641c910b055224ef56f45964baf3 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 25 Jul 2024 12:18:54 +0200 Subject: [PATCH 21/24] Update CHANGELOG.md --- CHANGELOG.md | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7967e697db..592e59f899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,6 @@ # Changelog -## 8.5.0 - -### Features - -- Add dart platform to sentry frames ([#2193](https://github.com/getsentry/sentry-dart/pull/2193)) - - This allows viewing the correct dart formatted raw stacktrace in the Sentry UI -- Support `ignoredExceptionsForType` ([#2150](https://github.com/getsentry/sentry-dart/pull/2150)) - - Filter out exception types by calling `SentryOptions.addExceptionFilterForType(Type exceptionType)` - -### Fixes - -- Disable sff & frame delay detection on web, linux and windows ([#2182](https://github.com/getsentry/sentry-dart/pull/2182)) - - Display refresh rate is locked at 60 for these platforms which can lead to inaccurate metrics +## Unreleased ### Improvements @@ -39,6 +27,23 @@ class MyCustomExceptionIdentifier implements ExceptionIdentifier { SentryFlutter.init((options) => options..prependExceptionTypeIdentifier(MyCustomExceptionIdentifier())); ``` + +## 8.5.0 + +### Features + +- Add dart platform to sentry frames ([#2193](https://github.com/getsentry/sentry-dart/pull/2193)) + - This allows viewing the correct dart formatted raw stacktrace in the Sentry UI +- Support `ignoredExceptionsForType` ([#2150](https://github.com/getsentry/sentry-dart/pull/2150)) + - Filter out exception types by calling `SentryOptions.addExceptionFilterForType(Type exceptionType)` + +### Fixes + +- Disable sff & frame delay detection on web, linux and windows ([#2182](https://github.com/getsentry/sentry-dart/pull/2182)) + - Display refresh rate is locked at 60 for these platforms which can lead to inaccurate metrics + +### Improvements + - Capture meaningful stack traces when unhandled errors have empty or missing stack traces ([#2152](https://github.com/getsentry/sentry-dart/pull/2152)) - This will affect grouping for unhandled errors that have empty or missing stack traces. From 1845eba08e9e468404e175ef3b95eb4979593be8 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 25 Jul 2024 12:24:00 +0200 Subject: [PATCH 22/24] Fix analyze --- flutter/test/sentry_flutter_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/test/sentry_flutter_test.dart b/flutter/test/sentry_flutter_test.dart index efb38e4ace..339d9cc514 100644 --- a/flutter/test/sentry_flutter_test.dart +++ b/flutter/test/sentry_flutter_test.dart @@ -665,7 +665,7 @@ void main() { test( 'should add DartExceptionTypeIdentifier and FlutterExceptionTypeIdentifier by default', () async { - SentryOptions? actualOptions = null; + SentryOptions? actualOptions; await SentryFlutter.init( (options) { options.dsn = fakeDsn; From b15dcf89b02cba1c622efa36aa8465a6ed320c06 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 25 Jul 2024 13:15:15 +0200 Subject: [PATCH 23/24] try fix test --- flutter/test/sentry_flutter_test.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flutter/test/sentry_flutter_test.dart b/flutter/test/sentry_flutter_test.dart index 339d9cc514..c05bf7b7cf 100644 --- a/flutter/test/sentry_flutter_test.dart +++ b/flutter/test/sentry_flutter_test.dart @@ -636,6 +636,8 @@ void main() { await SentryFlutter.resumeAppHangTracking(); verify(SentryFlutter.native?.resumeAppHangTracking()).called(1); + + SentryFlutter.native = null; }); test('resumeAppHangTracking does nothing when native is null', () async { @@ -653,6 +655,8 @@ void main() { await SentryFlutter.pauseAppHangTracking(); verify(SentryFlutter.native?.pauseAppHangTracking()).called(1); + + SentryFlutter.native = null; }); test('pauseAppHangTracking does nothing when native is null', () async { From 95d53f9a77f40e76027448fe27baba0bc88c4813 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 25 Jul 2024 13:42:28 +0200 Subject: [PATCH 24/24] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 592e59f899..2b6e3433d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ // How to add your own custom exception identifier class MyCustomExceptionIdentifier implements ExceptionIdentifier { @override - String identify(Exception exception) { + String? identifyType(Exception exception) { if (exception is MyCustomException) { return 'MyCustomException'; }