Commit ad19996609a85d5760a5195125dfec64edd9fe76
1 parent
cf6c3a4f
feat(ui):修复登录卡住的Bug
(cherry picked from commit 8556a032) # Conflicts: # lib/app/modules/login/controllers/login_controller.dart # lib/data/local/user_preferences_storage.dart
Showing
3 changed files
with
221 additions
and
62 deletions
| @@ -8,27 +8,26 @@ import 'package:doublefeel_flutter/app/routes/app_pages.dart'; | @@ -8,27 +8,26 @@ import 'package:doublefeel_flutter/app/routes/app_pages.dart'; | ||
| 8 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; | 8 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; |
| 9 | import 'package:doublefeel_flutter/core/constants/app_const.dart'; | 9 | import 'package:doublefeel_flutter/core/constants/app_const.dart'; |
| 10 | import 'package:doublefeel_flutter/core/logging/app_logger.dart'; | 10 | import 'package:doublefeel_flutter/core/logging/app_logger.dart'; |
| 11 | +import 'package:doublefeel_flutter/core/services/app_config_service.dart'; | ||
| 12 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 13 | +import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | ||
| 14 | +import 'package:doublefeel_flutter/pigeon/wechat_api.g.dart'; | ||
| 15 | +import 'package:flutter/services.dart'; | ||
| 16 | +import 'package:flutter/widgets.dart'; | ||
| 17 | +import 'package:get/get.dart'; | ||
| 18 | +import 'package:doublefeel_flutter/core/util/app_toast.dart'; | ||
| 11 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; | 19 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; |
| 12 | import 'package:doublefeel_flutter/core/network/api/vip_api.dart'; | 20 | import 'package:doublefeel_flutter/core/network/api/vip_api.dart'; |
| 13 | import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart'; | 21 | import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart'; |
| 14 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 22 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 15 | -import 'package:doublefeel_flutter/core/services/app_config_service.dart'; | ||
| 16 | import 'package:doublefeel_flutter/core/services/loading_service.dart'; | 23 | import 'package:doublefeel_flutter/core/services/loading_service.dart'; |
| 17 | -import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 18 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | 24 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; |
| 19 | -import 'package:doublefeel_flutter/core/util/app_toast.dart'; | ||
| 20 | import 'package:doublefeel_flutter/data/local/local_storage.dart'; | 25 | import 'package:doublefeel_flutter/data/local/local_storage.dart'; |
| 21 | import 'package:doublefeel_flutter/data/local/user_account_storage.dart'; | 26 | import 'package:doublefeel_flutter/data/local/user_account_storage.dart'; |
| 22 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; | 27 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; |
| 23 | -import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; | ||
| 24 | import 'package:doublefeel_flutter/data/models/user/user_models.dart'; | 28 | import 'package:doublefeel_flutter/data/models/user/user_models.dart'; |
| 25 | import 'package:doublefeel_flutter/data/models/vip/vip_info.dart'; | 29 | import 'package:doublefeel_flutter/data/models/vip/vip_info.dart'; |
| 26 | -import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | ||
| 27 | -import 'package:doublefeel_flutter/pigeon/wechat_api.g.dart'; | ||
| 28 | -import 'package:flutter/services.dart'; | ||
| 29 | -import 'package:flutter/widgets.dart'; | ||
| 30 | import 'package:flutter_timezone/flutter_timezone.dart'; | 30 | import 'package:flutter_timezone/flutter_timezone.dart'; |
| 31 | -import 'package:get/get.dart'; | ||
| 32 | 31 | ||
| 33 | class LoginController extends GetxController { | 32 | class LoginController extends GetxController { |
| 34 | final UserApi _userApi = Get.find<UserApi>(); | 33 | final UserApi _userApi = Get.find<UserApi>(); |
| @@ -370,21 +369,39 @@ class LoginController extends GetxController { | @@ -370,21 +369,39 @@ class LoginController extends GetxController { | ||
| 370 | } | 369 | } |
| 371 | 370 | ||
| 372 | isLoggingIn.value = true; | 371 | isLoggingIn.value = true; |
| 372 | + final loginStopwatch = Stopwatch()..start(); | ||
| 373 | + _logLoginTiming('phone_login.start'); | ||
| 373 | try { | 374 | try { |
| 374 | await LoadingService.instance.run(() async { | 375 | await LoadingService.instance.run(() async { |
| 375 | - final loginRes = await _userApi.login( | ||
| 376 | - telephone: cleanPhone, | ||
| 377 | - verifyCode: codeInput.value, | 376 | + final loginRes = await _measureLoginStep( |
| 377 | + 'phone_login.api', | ||
| 378 | + () => _userApi.login( | ||
| 379 | + telephone: cleanPhone, | ||
| 380 | + verifyCode: codeInput.value, | ||
| 381 | + ), | ||
| 378 | ); | 382 | ); |
| 379 | 383 | ||
| 380 | if (loginRes is AppSuccess<LoginResponse>) { | 384 | if (loginRes is AppSuccess<LoginResponse>) { |
| 381 | - await _handleLoginSuccess(loginRes.data); | ||
| 382 | - await Get.find<LocalStorage>().setLastLoginMethod('phone'); | ||
| 383 | - await environmentConfig.updateHideChooseRegion(true); | 385 | + await _measureLoginStep( |
| 386 | + 'phone_login.handle_success', | ||
| 387 | + () => _handleLoginSuccess(loginRes.data), | ||
| 388 | + ); | ||
| 389 | + await _measureLoginStep( | ||
| 390 | + 'phone_login.persist_login_method', | ||
| 391 | + () => Get.find<LocalStorage>().setLastLoginMethod('phone'), | ||
| 392 | + ); | ||
| 393 | + await _measureLoginStep( | ||
| 394 | + 'phone_login.persist_region_choice', | ||
| 395 | + () => environmentConfig.updateHideChooseRegion(true), | ||
| 396 | + ); | ||
| 384 | } | 397 | } |
| 385 | }); | 398 | }); |
| 386 | } finally { | 399 | } finally { |
| 387 | isLoggingIn.value = false; | 400 | isLoggingIn.value = false; |
| 401 | + loginStopwatch.stop(); | ||
| 402 | + _logLoginTiming( | ||
| 403 | + 'phone_login.finish total_ms=${loginStopwatch.elapsedMilliseconds}', | ||
| 404 | + ); | ||
| 388 | } | 405 | } |
| 389 | } | 406 | } |
| 390 | 407 | ||
| @@ -585,7 +602,10 @@ class LoginController extends GetxController { | @@ -585,7 +602,10 @@ class LoginController extends GetxController { | ||
| 585 | } | 602 | } |
| 586 | } | 603 | } |
| 587 | 604 | ||
| 588 | - final regRes = await _userApi.register(registerData); | 605 | + final regRes = await _measureLoginStep( |
| 606 | + 'login_success.register', | ||
| 607 | + () => _userApi.register(registerData), | ||
| 608 | + ); | ||
| 589 | if (regRes is AppSuccess<RegisterResponse>) { | 609 | if (regRes is AppSuccess<RegisterResponse>) { |
| 590 | accessToken = regRes.data.accessToken; | 610 | accessToken = regRes.data.accessToken; |
| 591 | } else { | 611 | } else { |
| @@ -595,12 +615,21 @@ class LoginController extends GetxController { | @@ -595,12 +615,21 @@ class LoginController extends GetxController { | ||
| 595 | } | 615 | } |
| 596 | 616 | ||
| 597 | // Fetch user info and VIP info | 617 | // Fetch user info and VIP info |
| 598 | - final userInfoResFuture = _userApi.getUserInfo(accessToken: accessToken); | 618 | + final userInfoResFuture = _measureLoginStep( |
| 619 | + 'login_success.get_user_info', | ||
| 620 | + () => _userApi.getUserInfo(accessToken: accessToken), | ||
| 621 | + ); | ||
| 599 | final vipInfoResFuture = isRegister | 622 | final vipInfoResFuture = isRegister |
| 600 | ? Future.value(null) | 623 | ? Future.value(null) |
| 601 | - : _vipApi.getVipInfo(accessToken: accessToken); | 624 | + : _measureLoginStep( |
| 625 | + 'login_success.get_vip_info', | ||
| 626 | + () => _vipApi.getVipInfo(accessToken: accessToken), | ||
| 627 | + ); | ||
| 602 | 628 | ||
| 603 | - final results = await Future.wait([userInfoResFuture, vipInfoResFuture]); | 629 | + final results = await _measureLoginStep( |
| 630 | + 'login_success.wait_user_and_vip', | ||
| 631 | + () => Future.wait([userInfoResFuture, vipInfoResFuture]), | ||
| 632 | + ); | ||
| 604 | final userInfoRes = results[0]; | 633 | final userInfoRes = results[0]; |
| 605 | final vipInfoRes = results[1]; | 634 | final vipInfoRes = results[1]; |
| 606 | 635 | ||
| @@ -621,51 +650,36 @@ class LoginController extends GetxController { | @@ -621,51 +650,36 @@ class LoginController extends GetxController { | ||
| 621 | vip = vipInfoRes.data; | 650 | vip = vipInfoRes.data; |
| 622 | } | 651 | } |
| 623 | 652 | ||
| 624 | - await _userPrefs.updateFromLogin( | ||
| 625 | - accessToken: accessToken, | ||
| 626 | - me: me, | ||
| 627 | - partner: partner, | ||
| 628 | - vip: vip, | 653 | + await _measureLoginStep( |
| 654 | + 'login_success.persist_user_preferences', | ||
| 655 | + () => _userPrefs.updateFromLogin( | ||
| 656 | + accessToken: accessToken, | ||
| 657 | + me: me, | ||
| 658 | + partner: partner, | ||
| 659 | + vip: vip, | ||
| 660 | + ), | ||
| 629 | ); | 661 | ); |
| 630 | 662 | ||
| 631 | - try { | ||
| 632 | - if (Get.isRegistered<AppEnvironmentConfig>()) { | ||
| 633 | - var env = Get.find<AppEnvironmentConfig>(); | ||
| 634 | - unawaited( | ||
| 635 | - AppPlatformHostApi() | ||
| 636 | - .updateLoginInfo( | ||
| 637 | - jsonEncode(UserPreferences( | ||
| 638 | - accessToken: accessToken, | ||
| 639 | - meUserInfo: me, | ||
| 640 | - partnerUserInfo: partner, | ||
| 641 | - vipInfo: vip != null | ||
| 642 | - ? UserPreferencesVipInfo.fromVipInfo(vip) | ||
| 643 | - : null, | ||
| 644 | - )), | ||
| 645 | - env.serverBaseUrl, | ||
| 646 | - ) | ||
| 647 | - .catchError((Object error, StackTrace stackTrace) { | ||
| 648 | - AppLogger.e('Native login info sync failed', error, stackTrace); | ||
| 649 | - }), | ||
| 650 | - ); | ||
| 651 | - } | ||
| 652 | - } on Exception catch (e) { | ||
| 653 | - AppLogger.e(e); | ||
| 654 | - } | ||
| 655 | - | ||
| 656 | - await _userStateService.onLogin(); | 663 | + await _measureLoginStep( |
| 664 | + 'login_success.user_state_on_login', | ||
| 665 | + _userStateService.onLogin, | ||
| 666 | + ); | ||
| 657 | if (isRegister) { | 667 | if (isRegister) { |
| 658 | ta.track('success_regist'); | 668 | ta.track('success_regist'); |
| 659 | } | 669 | } |
| 660 | 670 | ||
| 661 | // 用户登录成功即代表同意了协议,持久化到本地供冷启动时 SDK 初始化判断使用 | 671 | // 用户登录成功即代表同意了协议,持久化到本地供冷启动时 SDK 初始化判断使用 |
| 662 | - await Get.find<LocalStorage>().setTermsAgreed(true); | 672 | + await _measureLoginStep( |
| 673 | + 'login_success.persist_terms_agreed', | ||
| 674 | + () => Get.find<LocalStorage>().setTermsAgreed(true), | ||
| 675 | + ); | ||
| 663 | 676 | ||
| 664 | // 根据引导完成状态决定跳转目标 | 677 | // 根据引导完成状态决定跳转目标 |
| 665 | final userId = me.id ?? 0; | 678 | final userId = me.id ?? 0; |
| 666 | if (_userAccount.hasCompletedOnboarding(userId) || | 679 | if (_userAccount.hasCompletedOnboarding(userId) || |
| 667 | me.isPassNoviceGuide == 1) { | 680 | me.isPassNoviceGuide == 1) { |
| 668 | // 该账号已完成引导,直接进主页 | 681 | // 该账号已完成引导,直接进主页 |
| 682 | + _logLoginTiming('login_success.route_home'); | ||
| 669 | Get.offAllNamed(AppRoutes.home); | 683 | Get.offAllNamed(AppRoutes.home); |
| 670 | } else { | 684 | } else { |
| 671 | // 新用户或未完成引导,进入引导页(支持断点续做) | 685 | // 新用户或未完成引导,进入引导页(支持断点续做) |
| @@ -679,4 +693,30 @@ class LoginController extends GetxController { | @@ -679,4 +693,30 @@ class LoginController extends GetxController { | ||
| 679 | // userInfoRes 失败,外层 finally 会重置 isLoggingIn | 693 | // userInfoRes 失败,外层 finally 会重置 isLoggingIn |
| 680 | } | 694 | } |
| 681 | } | 695 | } |
| 696 | + | ||
| 697 | + Future<T> _measureLoginStep<T>( | ||
| 698 | + String name, | ||
| 699 | + Future<T> Function() operation, | ||
| 700 | + ) async { | ||
| 701 | + if (!environmentConfig.isDebug) return operation(); | ||
| 702 | + final stopwatch = Stopwatch()..start(); | ||
| 703 | + AppLogger.i('[LoginTiming] $name.start'); | ||
| 704 | + try { | ||
| 705 | + return await operation(); | ||
| 706 | + } catch (error, stackTrace) { | ||
| 707 | + AppLogger.e('[LoginTiming] $name.error', error, stackTrace); | ||
| 708 | + rethrow; | ||
| 709 | + } finally { | ||
| 710 | + stopwatch.stop(); | ||
| 711 | + AppLogger.i( | ||
| 712 | + '[LoginTiming] $name.finish elapsed_ms=${stopwatch.elapsedMilliseconds}', | ||
| 713 | + ); | ||
| 714 | + } | ||
| 715 | + } | ||
| 716 | + | ||
| 717 | + void _logLoginTiming(String message) { | ||
| 718 | + if (environmentConfig.isDebug) { | ||
| 719 | + AppLogger.i('[LoginTiming] $message'); | ||
| 720 | + } | ||
| 721 | + } | ||
| 682 | } | 722 | } |
| @@ -51,18 +51,85 @@ class UserStateService { | @@ -51,18 +51,85 @@ class UserStateService { | ||
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | Future<void> onLogin() async { | 53 | Future<void> onLogin() async { |
| 54 | - await initializeSdks(); | 54 | + final totalStopwatch = Stopwatch()..start(); |
| 55 | + _logLoginTiming('on_login.start'); | ||
| 56 | + await _measureLoginStep('on_login.initialize_sdks', initializeSdks); | ||
| 55 | _trackLogin(); | 57 | _trackLogin(); |
| 56 | _setUserProfile(); | 58 | _setUserProfile(); |
| 57 | - // The WebSocket handler can immediately start a calculation. Ensure its | ||
| 58 | - // local result store is ready before subscribing and connecting. | ||
| 59 | - await _healthRawDataCoreService.openDatabase(); | 59 | + // Opening/migrating the local health SQLite database can contend with the |
| 60 | + // native uploader. Do not make the login route wait for it. The dependent | ||
| 61 | + // realtime session is still started only after the database is ready. | ||
| 62 | + unawaited(_startRealtimeSessionAfterHealthDatabaseReady(totalStopwatch)); | ||
| 63 | + _logLoginTiming('on_login.database_initialization_dispatched'); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + Future<void> _startRealtimeSessionAfterHealthDatabaseReady( | ||
| 67 | + Stopwatch totalStopwatch, | ||
| 68 | + ) async { | ||
| 69 | + try { | ||
| 70 | + await _measureLoginStep( | ||
| 71 | + 'on_login.open_health_database', | ||
| 72 | + _healthRawDataCoreService.openDatabase, | ||
| 73 | + ); | ||
| 74 | + } catch (error, stackTrace) { | ||
| 75 | + AppLogger.e( | ||
| 76 | + '[LoginTiming] on_login.open_health_database.failed', | ||
| 77 | + error, | ||
| 78 | + stackTrace, | ||
| 79 | + ); | ||
| 80 | + return; | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + // The user may have logged out while an existing database lock cleared. | ||
| 84 | + if (!_userPrefs.isLoggedIn) { | ||
| 85 | + _logLoginTiming('on_login.realtime_session_skipped_logged_out'); | ||
| 86 | + return; | ||
| 87 | + } | ||
| 88 | + | ||
| 60 | // Push Kit may wait for its service/network. The token is still reported | 89 | // Push Kit may wait for its service/network. The token is still reported |
| 61 | // after login, but must not delay the rest of the authenticated session. | 90 | // after login, but must not delay the rest of the authenticated session. |
| 62 | unawaited(_pushService.registerPushToken()); | 91 | unawaited(_pushService.registerPushToken()); |
| 63 | - await _imService.connect(); | 92 | + try { |
| 93 | + await _measureLoginStep('on_login.im_connect', _imService.connect); | ||
| 94 | + } catch (error, stackTrace) { | ||
| 95 | + AppLogger.e( | ||
| 96 | + '[LoginTiming] on_login.im_connect.failed', error, stackTrace); | ||
| 97 | + } | ||
| 98 | + if (!_userPrefs.isLoggedIn) { | ||
| 99 | + _logLoginTiming('on_login.realtime_session_stopped_logged_out'); | ||
| 100 | + return; | ||
| 101 | + } | ||
| 64 | _listenForHealthDataUpdates(); | 102 | _listenForHealthDataUpdates(); |
| 65 | _webSocketService.start(); | 103 | _webSocketService.start(); |
| 104 | + totalStopwatch.stop(); | ||
| 105 | + _logLoginTiming( | ||
| 106 | + 'on_login.finish total_ms=${totalStopwatch.elapsedMilliseconds}'); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + Future<T> _measureLoginStep<T>( | ||
| 110 | + String name, | ||
| 111 | + Future<T> Function() operation, | ||
| 112 | + ) async { | ||
| 113 | + if (!_environmentConfig.isDebug) return operation(); | ||
| 114 | + final stopwatch = Stopwatch()..start(); | ||
| 115 | + AppLogger.i('[LoginTiming] $name.start'); | ||
| 116 | + try { | ||
| 117 | + return await operation(); | ||
| 118 | + } catch (error, stackTrace) { | ||
| 119 | + AppLogger.e('[LoginTiming] $name.error', error, stackTrace); | ||
| 120 | + rethrow; | ||
| 121 | + } finally { | ||
| 122 | + stopwatch.stop(); | ||
| 123 | + AppLogger.i( | ||
| 124 | + '[LoginTiming] $name.finish elapsed_ms=${stopwatch.elapsedMilliseconds}', | ||
| 125 | + ); | ||
| 126 | + } | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + void _logLoginTiming(String message) { | ||
| 130 | + if (_environmentConfig.isDebug) { | ||
| 131 | + AppLogger.i('[LoginTiming] $message'); | ||
| 132 | + } | ||
| 66 | } | 133 | } |
| 67 | 134 | ||
| 68 | Future<void> onLogout({bool callServerLogout = true}) async { | 135 | Future<void> onLogout({bool callServerLogout = true}) async { |
| @@ -65,25 +65,77 @@ class UserPreferencesStorage { | @@ -65,25 +65,77 @@ class UserPreferencesStorage { | ||
| 65 | 65 | ||
| 66 | Future<void> _syncLoginInfoToNative( | 66 | Future<void> _syncLoginInfoToNative( |
| 67 | {required UserPreferences userInfo}) async { | 67 | {required UserPreferences userInfo}) async { |
| 68 | + final totalStopwatch = Stopwatch()..start(); | ||
| 69 | + _logLoginTiming('native_login_sync.start'); | ||
| 68 | if (userInfo.accessToken.isEmpty) { | 70 | if (userInfo.accessToken.isEmpty) { |
| 69 | try { | 71 | try { |
| 70 | if (Get.isRegistered<AppEnvironmentConfig>()) { | 72 | if (Get.isRegistered<AppEnvironmentConfig>()) { |
| 71 | - await AppPlatformHostApi().logout(); | 73 | + await _measureLoginStep( |
| 74 | + 'native_login_sync.logout', | ||
| 75 | + () => AppPlatformHostApi().logout(), | ||
| 76 | + ); | ||
| 72 | } | 77 | } |
| 73 | } on Exception { | 78 | } on Exception { |
| 74 | return; | 79 | return; |
| 80 | + } finally { | ||
| 81 | + totalStopwatch.stop(); | ||
| 82 | + _logLoginTiming( | ||
| 83 | + 'native_login_sync.finish total_ms=${totalStopwatch.elapsedMilliseconds}', | ||
| 84 | + ); | ||
| 75 | } | 85 | } |
| 76 | return; | 86 | return; |
| 77 | } | 87 | } |
| 78 | try { | 88 | try { |
| 79 | if (Get.isRegistered<AppEnvironmentConfig>()) { | 89 | if (Get.isRegistered<AppEnvironmentConfig>()) { |
| 80 | var env = Get.find<AppEnvironmentConfig>(); | 90 | var env = Get.find<AppEnvironmentConfig>(); |
| 81 | - await AppPlatformHostApi() | ||
| 82 | - .updateLoginInfo(jsonEncode(userInfo), env.serverBaseUrl); | ||
| 83 | - await _syncHealthRawDatabaseToNative(); | 91 | + await _measureLoginStep( |
| 92 | + 'native_login_sync.update_login_info', | ||
| 93 | + () => AppPlatformHostApi() | ||
| 94 | + .updateLoginInfo(jsonEncode(userInfo), env.serverBaseUrl), | ||
| 95 | + ); | ||
| 96 | + await _measureLoginStep( | ||
| 97 | + 'native_login_sync.sync_health_database', | ||
| 98 | + _syncHealthRawDatabaseToNative, | ||
| 99 | + ); | ||
| 84 | } | 100 | } |
| 85 | - } on Exception { | ||
| 86 | - return; | 101 | + AppLogger.d('Native login info synced for cold start'); |
| 102 | + } catch (error, stackTrace) { | ||
| 103 | + AppLogger.e('Native login info sync failed', error, stackTrace); | ||
| 104 | + } finally { | ||
| 105 | + totalStopwatch.stop(); | ||
| 106 | + _logLoginTiming( | ||
| 107 | + 'native_login_sync.finish total_ms=${totalStopwatch.elapsedMilliseconds}', | ||
| 108 | + ); | ||
| 109 | + } | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + bool get _isDebug => | ||
| 113 | + Get.isRegistered<AppEnvironmentConfig>() && | ||
| 114 | + Get.find<AppEnvironmentConfig>().isDebug; | ||
| 115 | + | ||
| 116 | + Future<T> _measureLoginStep<T>( | ||
| 117 | + String name, | ||
| 118 | + Future<T> Function() operation, | ||
| 119 | + ) async { | ||
| 120 | + if (!_isDebug) return operation(); | ||
| 121 | + final stopwatch = Stopwatch()..start(); | ||
| 122 | + AppLogger.i('[LoginTiming] $name.start'); | ||
| 123 | + try { | ||
| 124 | + return await operation(); | ||
| 125 | + } catch (error, stackTrace) { | ||
| 126 | + AppLogger.e('[LoginTiming] $name.error', error, stackTrace); | ||
| 127 | + rethrow; | ||
| 128 | + } finally { | ||
| 129 | + stopwatch.stop(); | ||
| 130 | + AppLogger.i( | ||
| 131 | + '[LoginTiming] $name.finish elapsed_ms=${stopwatch.elapsedMilliseconds}', | ||
| 132 | + ); | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + void _logLoginTiming(String message) { | ||
| 137 | + if (_isDebug) { | ||
| 138 | + AppLogger.i('[LoginTiming] $message'); | ||
| 87 | } | 139 | } |
| 88 | } | 140 | } |
| 89 | 141 |
-
Please register or login to post a comment