From 8ec9f7f96437a5c671ba830cc6c134eb527d67c8 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Wed, 4 Oct 2023 18:06:48 +0330 Subject: [PATCH] Fix bugs --- assets/translations/strings.i18n.json | 10 ++- assets/translations/strings_fa.i18n.json | 8 +- lib/bootstrap.dart | 2 - lib/data/data_providers.dart | 3 +- .../repository/profiles_repository_impl.dart | 4 +- .../connectivity/connection_failure.dart | 6 +- lib/domain/core_service_failure.dart | 7 +- lib/domain/failures.dart | 81 ++++++++++--------- lib/domain/profiles/profile.dart | 1 + lib/domain/profiles/profiles_failure.dart | 7 +- lib/features/about/view/about_page.dart | 2 +- lib/features/common/common_controllers.dart | 4 +- lib/features/common/new_version_dialog.dart | 25 +++--- lib/features/common/profile_tile.dart | 2 +- lib/features/home/view/home_page.dart | 2 +- lib/features/intro/view/intro_page.dart | 12 ++- lib/features/logs/view/logs_page.dart | 2 +- .../view/profile_detail_page.dart | 14 +++- .../profiles/view/add_profile_modal.dart | 4 +- .../profiles/view/profiles_modal.dart | 2 +- lib/features/proxies/view/proxies_page.dart | 4 +- lib/services/cron_service.dart | 6 +- lib/utils/alerts.dart | 1 + lib/utils/sentry_loggy_integration.dart | 2 +- lib/utils/sentry_utils.dart | 8 +- 25 files changed, 130 insertions(+), 89 deletions(-) diff --git a/assets/translations/strings.i18n.json b/assets/translations/strings.i18n.json index 99bdf526..6f1ac492 100644 --- a/assets/translations/strings.i18n.json +++ b/assets/translations/strings.i18n.json @@ -57,12 +57,13 @@ "fromClipboard": "Add From Clipboard", "scanQr": "Scan QR code", "manually": "Manual Entry", - "addingProfileMsg": "Adding Profile" + "addingProfileMsg": "Adding Profile", + "failureMsg": "Failed to add profile" }, "update": { "buttonTxt": "Update", "tooltip": "Update Profile", - "failureMsg": "Update Failed", + "failureMsg": "Failed to update profile", "successMsg": "Profile updated successfully" }, "edit": { @@ -76,7 +77,8 @@ }, "save": { "buttonText": "Save", - "successMsg": "Profile saved successfully" + "successMsg": "Profile saved successfully", + "failureMsg": "Failed to save profile" }, "detailsForm": { "nameLabel": "Name", @@ -242,7 +244,7 @@ "invalidConfig": "Invalid Configs" }, "connection": { - "unexpected": "Unexpected error", + "unexpected": "Unexpected connection error", "timeout": "Connection timeout", "badCertificate": "Bad certificate", "badResponse": "Bad response", diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json index db357f0b..cb34ee84 100644 --- a/assets/translations/strings_fa.i18n.json +++ b/assets/translations/strings_fa.i18n.json @@ -57,7 +57,8 @@ "fromClipboard": "افزودن از کلیپ‌بورد", "scanQr": "اسکن QR کد", "manually": "افزودن دستی", - "addingProfileMsg": "در حال افزودن پروفایل" + "addingProfileMsg": "در حال افزودن پروفایل", + "failureMsg": "در افزودن پروفایل خطایی رخ داد" }, "update": { "buttonTxt": "بروزرسانی", @@ -76,7 +77,8 @@ }, "save": { "buttonText": "ذخیره", - "successMsg": "پروفایل با موفقیت ذخیره شد" + "successMsg": "پروفایل با موفقیت ذخیره شد", + "failureMsg": "خطا در ذخیره پروفایل" }, "detailsForm": { "nameLabel": "نام", @@ -242,7 +244,7 @@ "invalidConfig": "کانفیگ غیر معتبر" }, "connection": { - "unexpected": "خطای غیرمنتظره", + "unexpected": " خطای غیرمنتظره در اتصال", "timeout": "درخواست بیش از حد مجاز زمان برد", "badCertificate": "خطای اعتبار سنجی", "badResponse": "پاسخ نامعتبر", diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 8c6d717b..071d395c 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -10,7 +10,6 @@ import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/data/data_providers.dart'; import 'package:hiddify/data/repository/app_repository_impl.dart'; import 'package:hiddify/domain/environment.dart'; -import 'package:hiddify/domain/failures.dart'; import 'package:hiddify/features/common/active_profile/active_profile_notifier.dart'; import 'package:hiddify/features/common/window/window_controller.dart'; import 'package:hiddify/features/system_tray/system_tray.dart'; @@ -108,7 +107,6 @@ Future _lazyBootstrap( runApp( ProviderScope( parent: container, - observers: [SentryRiverpodObserver()], child: SentryUserInteractionWidget( child: const AppView(), ), diff --git a/lib/data/data_providers.dart b/lib/data/data_providers.dart index 6790d425..233777e6 100644 --- a/lib/data/data_providers.dart +++ b/lib/data/data_providers.dart @@ -12,7 +12,6 @@ import 'package:hiddify/domain/core_facade.dart'; import 'package:hiddify/domain/profiles/profiles.dart'; import 'package:hiddify/services/service_providers.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:sentry_dio/sentry_dio.dart'; import 'package:shared_preferences/shared_preferences.dart'; part 'data_providers.g.dart'; @@ -34,7 +33,7 @@ Dio dio(DioRef ref) => Dio( "User-Agent": ref.watch(appInfoProvider).userAgent, }, ), - )..addSentry(); + ); @Riverpod(keepAlive: true) ProfilesDao profilesDao(ProfilesDaoRef ref) => ProfilesDao( diff --git a/lib/data/repository/profiles_repository_impl.dart b/lib/data/repository/profiles_repository_impl.dart index d05ba757..afc14ae0 100644 --- a/lib/data/repository/profiles_repository_impl.dart +++ b/lib/data/repository/profiles_repository_impl.dart @@ -136,7 +136,7 @@ class ProfilesRepositoryImpl }, ); } finally { - if (await File(tempPath).exists()) await File(tempPath).delete(); + if (File(tempPath).existsSync()) File(tempPath).deleteSync(); } }, (error, stackTrace) { @@ -277,7 +277,7 @@ class ProfilesRepositoryImpl }, ); } finally { - if (await File(tempPath).exists()) await File(tempPath).delete(); + if (File(tempPath).existsSync()) File(tempPath).deleteSync(); } }, ); diff --git a/lib/domain/connectivity/connection_failure.dart b/lib/domain/connectivity/connection_failure.dart index 0004a1b9..b8b39f04 100644 --- a/lib/domain/connectivity/connection_failure.dart +++ b/lib/domain/connectivity/connection_failure.dart @@ -15,9 +15,11 @@ sealed class ConnectionFailure with _$ConnectionFailure, Failure { StackTrace? stackTrace, ]) = UnexpectedConnectionFailure; + @With() const factory ConnectionFailure.missingVpnPermission([String? message]) = MissingVpnPermission; + @With() const factory ConnectionFailure.missingNotificationPermission([ String? message, ]) = MissingNotificationPermission; @@ -28,9 +30,9 @@ sealed class ConnectionFailure with _$ConnectionFailure, Failure { @override ({String type, String? message}) present(TranslationsEn t) { return switch (this) { - UnexpectedConnectionFailure(:final error) => ( + UnexpectedConnectionFailure() => ( type: t.failure.connectivity.unexpected, - message: t.mayPrintError(error), + message: null, ), MissingVpnPermission(:final message) => ( type: t.failure.connectivity.missingVpnPermission, diff --git a/lib/domain/core_service_failure.dart b/lib/domain/core_service_failure.dart index 894ba578..627e3ed1 100644 --- a/lib/domain/core_service_failure.dart +++ b/lib/domain/core_service_failure.dart @@ -14,7 +14,7 @@ sealed class CoreServiceFailure with _$CoreServiceFailure, Failure { StackTrace? stackTrace, ) = UnexpectedCoreServiceFailure; - @With() + @With() const factory CoreServiceFailure.serviceNotRunning([String? message]) = CoreServiceNotRunning; @@ -22,6 +22,7 @@ sealed class CoreServiceFailure with _$CoreServiceFailure, Failure { String? message, ]) = InvalidConfigOptions; + @With() const factory CoreServiceFailure.invalidConfig([ String? message, ]) = InvalidConfig; @@ -51,9 +52,9 @@ sealed class CoreServiceFailure with _$CoreServiceFailure, Failure { @override ({String type, String? message}) present(TranslationsEn t) { return switch (this) { - UnexpectedCoreServiceFailure(:final error) => ( + UnexpectedCoreServiceFailure() => ( type: t.failure.singbox.unexpected, - message: t.mayPrintError(error), + message: null, ), CoreServiceNotRunning(:final message) => ( type: t.failure.singbox.serviceNotRunning, diff --git a/lib/domain/failures.dart b/lib/domain/failures.dart index 155c03a4..1640ac61 100644 --- a/lib/domain/failures.dart +++ b/lib/domain/failures.dart @@ -1,68 +1,73 @@ import 'package:dio/dio.dart'; import 'package:hiddify/core/prefs/prefs.dart'; +typedef PresentableError = ({String type, String? message}); + mixin Failure { ({String type, String? message}) present(TranslationsEn t); } +/// failures that are not expected to happen but depending on [error] type might not be relevant (eg network errors) mixin UnexpectedFailure { Object? get error; StackTrace? get stackTrace; } +/// failures that are expected to happen and should be handled by the app +/// and should be logged, eg missing permissions +mixin ExpectedMeasuredFailure {} + /// failures ignored by analytics service etc. -mixin ExpectedException {} +mixin ExpectedFailure {} extension ErrorPresenter on TranslationsEn { - String? _errorToMessage(Object error) { - switch (error) { - case Failure(): - final err = error.present(this); - return err.type + (err.message == null ? "" : ": ${err.message}"); - case DioException(): - return error.present(this); - default: - return null; - } - } + PresentableError errorToPair(Object error) => switch (error) { + UnexpectedFailure(error: final nestedErr?) => errorToPair(nestedErr), + Failure() => error.present(this), + DioException() => error.present(this), + _ => (type: failure.unexpected, message: null), + }; - String printError(Object error) => - _errorToMessage(error) ?? failure.unexpected; - - String? mayPrintError(Object? error) => - error != null ? _errorToMessage(error) : null; - - ({String type, String? message}) presentError( + PresentableError presentError( Object error, { String? action, }) { - final ({String type, String? message}) presentable; - if (error case Failure()) { - presentable = error.present(this); - } else { - presentable = (type: failure.unexpected, message: null); - } + final pair = errorToPair(error); + if (action == null) return pair; return ( - type: action == null ? presentable.type : "$action: ${presentable.type}", - message: presentable.message, + type: action, + message: pair.type + (pair.message == null ? "" : "\n${pair.message!}"), ); } + + String presentShortError( + Object error, { + String? action, + }) { + final pair = errorToPair(error); + if (action == null) return pair.type; + return "$action: ${pair.type}"; + } } extension DioExceptionPresenter on DioException { - String presentType(TranslationsEn t) => switch (type) { + PresentableError present(TranslationsEn t) => switch (type) { DioExceptionType.connectionTimeout || DioExceptionType.sendTimeout || DioExceptionType.receiveTimeout => - t.failure.connection.timeout, - DioExceptionType.badCertificate => t.failure.connection.badCertificate, - DioExceptionType.badResponse => t.failure.connection.badResponse, - DioExceptionType.connectionError => - t.failure.connection.connectionError, - _ => t.failure.unexpected, + (type: t.failure.connection.timeout, message: null), + DioExceptionType.badCertificate => ( + type: t.failure.connection.badCertificate, + message: message, + ), + DioExceptionType.badResponse => ( + type: t.failure.connection.badResponse, + message: message, + ), + DioExceptionType.connectionError => ( + type: t.failure.connection.connectionError, + message: message, + ), + _ => (type: t.failure.connection.unexpected, message: message), }; - - String present(TranslationsEn t) { - return presentType(t) + (message == null ? "" : "\n$message"); - } } diff --git a/lib/domain/profiles/profile.dart b/lib/domain/profiles/profile.dart index 2a542341..dd2bc952 100644 --- a/lib/domain/profiles/profile.dart +++ b/lib/domain/profiles/profile.dart @@ -34,6 +34,7 @@ sealed class Profile with _$Profile { required DateTime lastUpdate, }) = LocalProfile; + // ignore: prefer_constructors_over_static_methods static RemoteProfile fromResponse( String url, Map> headers, diff --git a/lib/domain/profiles/profiles_failure.dart b/lib/domain/profiles/profiles_failure.dart index fdb26cb4..7a9edb8a 100644 --- a/lib/domain/profiles/profiles_failure.dart +++ b/lib/domain/profiles/profiles_failure.dart @@ -16,18 +16,19 @@ sealed class ProfileFailure with _$ProfileFailure, Failure { const factory ProfileFailure.notFound() = ProfileNotFoundFailure; - @With() + @With() const factory ProfileFailure.invalidUrl() = ProfileInvalidUrlFailure; + @With() const factory ProfileFailure.invalidConfig([String? message]) = ProfileInvalidConfigFailure; @override ({String type, String? message}) present(TranslationsEn t) { return switch (this) { - ProfileUnexpectedFailure(:final error) => ( + ProfileUnexpectedFailure() => ( type: t.failure.profiles.unexpected, - message: t.mayPrintError(error), + message: null, ), ProfileNotFoundFailure() => ( type: t.failure.profiles.notFound, diff --git a/lib/features/about/view/about_page.dart b/lib/features/about/view/about_page.dart index ceea789f..d8630f15 100644 --- a/lib/features/about/view/about_page.dart +++ b/lib/features/about/view/about_page.dart @@ -31,7 +31,7 @@ class AboutPage extends HookConsumerWidget { canIgnore: false, ).show(context); case AppUpdateStateError(:final error): - return CustomToast.error(t.printError(error)).show(context); + return CustomToast.error(t.presentShortError(error)).show(context); case AppUpdateStateNotAvailable(): return CustomToast.success(t.appUpdate.notAvailableMsg) .show(context); diff --git a/lib/features/common/common_controllers.dart b/lib/features/common/common_controllers.dart index 07ace23b..8cfbb413 100644 --- a/lib/features/common/common_controllers.dart +++ b/lib/features/common/common_controllers.dart @@ -1,4 +1,3 @@ -import 'package:dartx/dartx.dart'; import 'package:hiddify/core/prefs/general_prefs.dart'; import 'package:hiddify/features/common/app_update_notifier.dart'; import 'package:hiddify/features/common/connectivity/connectivity_controller.dart'; @@ -20,8 +19,7 @@ void commonControllers(CommonControllersRef ref) { introCompletedProvider, (_, completed) async { if (completed) { - await Future.delayed(5.seconds) - .then((_) async => ref.read(cronServiceProvider).startScheduler()); + await ref.read(cronServiceProvider).startScheduler(); } }, fireImmediately: true, diff --git a/lib/features/common/new_version_dialog.dart b/lib/features/common/new_version_dialog.dart index 2f584afd..20ef11e0 100644 --- a/lib/features/common/new_version_dialog.dart +++ b/lib/features/common/new_version_dialog.dart @@ -7,23 +7,30 @@ import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; // TODO add release notes -class NewVersionDialog extends HookConsumerWidget { - const NewVersionDialog( +class NewVersionDialog extends HookConsumerWidget with PresLogger { + NewVersionDialog( this.currentVersion, this.newVersion, { - super.key, + // super.key, this.canIgnore = true, - }); + }) : super(key: _dialogKey); final String currentVersion; final RemoteVersionInfo newVersion; final bool canIgnore; - Future show(BuildContext context) { - return showDialog( - context: context, - builder: (context) => this, - ); + static final _dialogKey = GlobalKey(debugLabel: 'new version dialog'); + + Future show(BuildContext context) async { + if (_dialogKey.currentContext == null) { + return showDialog( + context: context, + useRootNavigator: true, + builder: (context) => this, + ); + } else { + loggy.warning("new version dialog is already open"); + } } @override diff --git a/lib/features/common/profile_tile.dart b/lib/features/common/profile_tile.dart index 564b99b3..ce77cb41 100644 --- a/lib/features/common/profile_tile.dart +++ b/lib/features/common/profile_tile.dart @@ -32,7 +32,7 @@ class ProfileTile extends HookConsumerWidget { final selectActiveMutation = useMutation( initialOnFailure: (err) { - CustomToast.error(t.printError(err)).show(context); + CustomToast.error(t.presentShortError(err)).show(context); }, initialOnSuccess: () { if (context.mounted) context.pop(); diff --git a/lib/features/home/view/home_page.dart b/lib/features/home/view/home_page.dart index ee9dceee..4d05bf97 100644 --- a/lib/features/home/view/home_page.dart +++ b/lib/features/home/view/home_page.dart @@ -68,7 +68,7 @@ class HomePage extends HookConsumerWidget { _ => const EmptyProfilesHomeBody(), }, AsyncError(:final error) => - SliverErrorBodyPlaceholder(t.printError(error)), + SliverErrorBodyPlaceholder(t.presentShortError(error)), _ => const SliverToBoxAdapter(), }, ], diff --git a/lib/features/intro/view/intro_page.dart b/lib/features/intro/view/intro_page.dart index b78084cb..1ded6d56 100644 --- a/lib/features/intro/view/intro_page.dart +++ b/lib/features/intro/view/intro_page.dart @@ -69,8 +69,16 @@ class IntroPage extends HookConsumerWidget with PresLogger { child: FilledButton( onPressed: () async { if (!ref.read(enableAnalyticsProvider)) { - loggy.debug("disabling analytics per user request"); - await Sentry.close(); + loggy.info("disabling analytics per user request"); + try { + await Sentry.close(); + } catch (error, stackTrace) { + loggy.warning( + "could not disable analytics", + error, + stackTrace, + ); + } } await ref .read(introCompletedProvider.notifier) diff --git a/lib/features/logs/view/logs_page.dart b/lib/features/logs/view/logs_page.dart index 830bcbaa..af2e7954 100644 --- a/lib/features/logs/view/logs_page.dart +++ b/lib/features/logs/view/logs_page.dart @@ -138,7 +138,7 @@ class LogsPage extends HookConsumerWidget with PresLogger { NestedTabAppBar( title: Text(t.logs.pageTitle), ), - SliverErrorBodyPlaceholder(t.printError(error)), + SliverErrorBodyPlaceholder(t.presentShortError(error)), ], ), ); diff --git a/lib/features/profile_detail/view/profile_detail_page.dart b/lib/features/profile_detail/view/profile_detail_page.dart index fe3c1c27..2c8eb0ff 100644 --- a/lib/features/profile_detail/view/profile_detail_page.dart +++ b/lib/features/profile_detail/view/profile_detail_page.dart @@ -29,7 +29,15 @@ class ProfileDetailPage extends HookConsumerWidget with PresLogger { if (asyncSave case AsyncData(value: final save)) { switch (save) { case MutationFailure(:final failure): - CustomAlertDialog.fromErr(t.presentError(failure)).show(context); + final String action; + if (ref.read(provider) case AsyncData(value: final data) + when data.isEditing) { + action = t.profile.save.failureMsg; + } else { + action = t.profile.add.failureMsg; + } + CustomAlertDialog.fromErr(t.presentError(failure, action: action)) + .show(context); case MutationSuccess(): CustomToast.success(t.profile.save.successMsg).show(context); WidgetsBinding.instance.addPostFrameCallback( @@ -62,7 +70,7 @@ class ProfileDetailPage extends HookConsumerWidget with PresLogger { if (asyncDelete case AsyncData(value: final delete)) { switch (delete) { case MutationFailure(:final failure): - CustomToast.error(t.printError(failure)).show(context); + CustomToast.error(t.presentShortError(failure)).show(context); case MutationSuccess(): CustomToast.success(t.profile.delete.successMsg).show(context); WidgetsBinding.instance.addPostFrameCallback( @@ -261,7 +269,7 @@ class ProfileDetailPage extends HookConsumerWidget with PresLogger { title: Text(t.profile.detailsPageTitle), pinned: true, ), - SliverErrorBodyPlaceholder(t.printError(error)), + SliverErrorBodyPlaceholder(t.presentShortError(error)), ], ), ); diff --git a/lib/features/profiles/view/add_profile_modal.dart b/lib/features/profiles/view/add_profile_modal.dart index ea65b258..a0ce2ecb 100644 --- a/lib/features/profiles/view/add_profile_modal.dart +++ b/lib/features/profiles/view/add_profile_modal.dart @@ -35,7 +35,9 @@ class AddProfileModal extends HookConsumerWidget { t.failure.profiles.invalidUrl, ).show(context); } else { - CustomAlertDialog.fromErr(t.presentError(err)).show(context); + CustomAlertDialog.fromErr( + t.presentError(err, action: t.profile.add.failureMsg), + ).show(context); } }, initialOnSuccess: () { diff --git a/lib/features/profiles/view/profiles_modal.dart b/lib/features/profiles/view/profiles_modal.dart index 081f68ff..20cae7f2 100644 --- a/lib/features/profiles/view/profiles_modal.dart +++ b/lib/features/profiles/view/profiles_modal.dart @@ -37,7 +37,7 @@ class ProfilesModal extends HookConsumerWidget { itemCount: profiles.length, ), AsyncError(:final error) => SliverErrorBodyPlaceholder( - t.printError(error), + t.presentShortError(error), ), AsyncLoading() => const SliverLoadingBodyPlaceholder(), _ => const SliverToBoxAdapter(), diff --git a/lib/features/proxies/view/proxies_page.dart b/lib/features/proxies/view/proxies_page.dart index 92144cb1..b42fc05a 100644 --- a/lib/features/proxies/view/proxies_page.dart +++ b/lib/features/proxies/view/proxies_page.dart @@ -20,7 +20,7 @@ class ProxiesPage extends HookConsumerWidget with PresLogger { final selectActiveProxyMutation = useMutation( initialOnFailure: (error) => - CustomToast.error(t.printError(error)).show(context), + CustomToast.error(t.presentShortError(error)).show(context), ); switch (asyncProxies) { @@ -144,7 +144,7 @@ class ProxiesPage extends HookConsumerWidget with PresLogger { title: Text(t.proxies.pageTitle), ), SliverErrorBodyPlaceholder( - t.printError(error), + t.presentShortError(error), icon: null, ), ], diff --git a/lib/services/cron_service.dart b/lib/services/cron_service.dart index ace624df..d3fe1cb9 100644 --- a/lib/services/cron_service.dart +++ b/lib/services/cron_service.dart @@ -55,9 +55,9 @@ class CronService with InfraLogger { int runCount = 0; _scheduler = NeatPeriodicTaskScheduler( name: "cron job scheduler", - interval: const Duration(minutes: 5), - timeout: const Duration(seconds: 15), - minCycle: const Duration(minutes: 1), + interval: const Duration(minutes: 10), + timeout: const Duration(minutes: 5), + minCycle: const Duration(minutes: 2), task: () { loggy.debug("in run ${runCount++}"); return Future.wait(jobs.values.map(run)); diff --git a/lib/utils/alerts.dart b/lib/utils/alerts.dart index e9a7add2..e209e438 100644 --- a/lib/utils/alerts.dart +++ b/lib/utils/alerts.dart @@ -20,6 +20,7 @@ class CustomAlertDialog extends StatelessWidget { Future show(BuildContext context) async { await showDialog( context: context, + useRootNavigator: true, builder: (context) => this, ); } diff --git a/lib/utils/sentry_loggy_integration.dart b/lib/utils/sentry_loggy_integration.dart index de4c4a0f..e9b20563 100644 --- a/lib/utils/sentry_loggy_integration.dart +++ b/lib/utils/sentry_loggy_integration.dart @@ -34,7 +34,7 @@ class SentryLoggyIntegration extends LoggyPrinter @override Future onLog(LogRecord record) async { - if (!canSendEvent(record.error)) return; + if (!canLogEvent(record.error)) return; if (_shouldLog(record.level, _minEventLevel)) { await _hub.captureEvent( diff --git a/lib/utils/sentry_utils.dart b/lib/utils/sentry_utils.dart index b83b82d3..9b250ced 100644 --- a/lib/utils/sentry_utils.dart +++ b/lib/utils/sentry_utils.dart @@ -15,7 +15,13 @@ bool canSendEvent(dynamic throwable) { UnexpectedFailure(:final error) => canSendEvent(error), DioException _ => false, SocketException _ => false, - ExpectedException _ => false, + ExpectedFailure _ => false, + ExpectedMeasuredFailure _ => false, _ => true, }; } + +bool canLogEvent(dynamic throwable) => switch (throwable) { + ExpectedMeasuredFailure _ => true, + _ => canSendEvent(throwable), + };