From 4d9287ae55cb19352a2a9f9e61749dded0c5a674 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Mon, 18 Nov 2024 15:03:03 +0100 Subject: [PATCH 01/14] feat: implement bugsee data obscure from reported videos --- src/app/.env.dev | 3 +- src/app/.env.prod | 3 +- src/app/.env.staging | 3 +- .../bugsee/bugsee_configuration_data.dart | 8 +- .../lib/access/bugsee/bugsee_repository.dart | 21 ++++ .../business/bugsee/bugsee_config_state.dart | 61 ++++++++++ .../lib/business/bugsee/bugsee_manager.dart | 110 ++++++++++-------- .../dad_jokes/dad_joke_list_item.dart | 13 ++- .../bugsee_configuration_widget.dart | 35 +++--- 9 files changed, 185 insertions(+), 72 deletions(-) create mode 100644 src/app/lib/business/bugsee/bugsee_config_state.dart diff --git a/src/app/.env.dev b/src/app/.env.dev index 22d74e66..40cb1016 100644 --- a/src/app/.env.dev +++ b/src/app/.env.dev @@ -5,4 +5,5 @@ DAD_JOKES_BASE_URL='https://www.reddit.com/r/dadjokes' APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=1 -DIAGNOSTIC_ENABLED=true \ No newline at end of file +DIAGNOSTIC_ENABLED=true +IS_DATA_OBSCURE=true \ No newline at end of file diff --git a/src/app/.env.prod b/src/app/.env.prod index e2c01228..9c22677e 100644 --- a/src/app/.env.prod +++ b/src/app/.env.prod @@ -5,4 +5,5 @@ DAD_JOKES_BASE_URL='https://www.reddit.com/r/dadjokes' APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=720 -DIAGNOSTIC_ENABLED=false \ No newline at end of file +DIAGNOSTIC_ENABLED=false +IS_DATA_OBSCURE=true \ No newline at end of file diff --git a/src/app/.env.staging b/src/app/.env.staging index 2412f27b..e549e665 100644 --- a/src/app/.env.staging +++ b/src/app/.env.staging @@ -5,4 +5,5 @@ DAD_JOKES_BASE_URL='https://www.reddit.com/r/dadjokes' APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=1 -DIAGNOSTIC_ENABLED=true \ No newline at end of file +DIAGNOSTIC_ENABLED=true +IS_DATA_OBSCURE=true \ No newline at end of file diff --git a/src/app/lib/access/bugsee/bugsee_configuration_data.dart b/src/app/lib/access/bugsee/bugsee_configuration_data.dart index 1255c78b..b636a424 100644 --- a/src/app/lib/access/bugsee/bugsee_configuration_data.dart +++ b/src/app/lib/access/bugsee/bugsee_configuration_data.dart @@ -5,8 +5,12 @@ final class BugseeConfigurationData { /// Indicate whether the video capturing feature in Bugsee is enabled or not. final bool? isVideoCaptureEnabled; + /// Indicate whether bugsee obscure application data in videos and images or not. + final bool? isDataObscrured; + const BugseeConfigurationData({ - required this.isBugseeEnabled, - required this.isVideoCaptureEnabled, + this.isBugseeEnabled, + this.isVideoCaptureEnabled, + this.isDataObscrured, }); } diff --git a/src/app/lib/access/bugsee/bugsee_repository.dart b/src/app/lib/access/bugsee/bugsee_repository.dart index 3f270eb3..74f32650 100644 --- a/src/app/lib/access/bugsee/bugsee_repository.dart +++ b/src/app/lib/access/bugsee/bugsee_repository.dart @@ -13,11 +13,15 @@ abstract interface class BugseeRepository { /// Update the current video captured or not flag in shared prefs. Future setIsVideoCaptureEnabled(bool isVideoCaptureEnabled); + + /// Update whether data is obscure in shared prefs. + Future setIsDataObscure(bool isDataObscure); } final class _BugseeRepository implements BugseeRepository { final String _bugseeEnabledKey = 'bugseeEnabledKey'; final String _videoCaptureKey = 'videoCaptureKey'; + final String _dataObscureKey = 'dataObscureKey'; @override Future getBugseeConfiguration() async { @@ -25,6 +29,7 @@ final class _BugseeRepository implements BugseeRepository { return BugseeConfigurationData( isBugseeEnabled: sharedPrefInstance.getBool(_bugseeEnabledKey), isVideoCaptureEnabled: sharedPrefInstance.getBool(_videoCaptureKey), + isDataObscrured: sharedPrefInstance.getBool(_dataObscureKey), ); } @@ -59,4 +64,20 @@ final class _BugseeRepository implements BugseeRepository { ); } } + + @override + Future setIsDataObscure(bool isDataObscured) async { + final sharedPrefInstance = await SharedPreferences.getInstance(); + + bool isSaved = await sharedPrefInstance.setBool( + _dataObscureKey, + isDataObscured, + ); + + if (!isSaved) { + throw PersistenceException( + message: 'Error while setting $_dataObscureKey $isDataObscured', + ); + } + } } diff --git a/src/app/lib/business/bugsee/bugsee_config_state.dart b/src/app/lib/business/bugsee/bugsee_config_state.dart new file mode 100644 index 00000000..24461222 --- /dev/null +++ b/src/app/lib/business/bugsee/bugsee_config_state.dart @@ -0,0 +1,61 @@ +import 'package:equatable/equatable.dart'; + +final class BugseeConfigState extends Equatable { + /// indicate if the app require a restart to reactivate the bugsee configurations + /// + /// `true` only if `isConfigurationValid == true` and bugsee is turned on + bool isRestartRequired; + + /// indicate if bugsee is enabled or not + /// by default bugsee is enabled if `isConfigurationValid == true`. + bool isBugseeEnabled; + + /// indicate whether video capturing is enabled or not. + /// enabled by default if `isBugseeEnabled == true`. + /// + /// cannot be true if `isBugseeEnabled == false`. + bool isVideoCaptureEnabled; + + /// indicate if bugsee configuration is valid + /// config is valid if app in release mode and the provided token is valid + /// following the [bugseeTokenFormat] regex. + bool isConfigurationValid; + + /// indicate whether data is obscured in report videos + /// + /// cannot be true if `isBugseeEnabled == false`. + bool isDataObscured; + + BugseeConfigState({ + this.isRestartRequired = false, + this.isBugseeEnabled = false, + this.isVideoCaptureEnabled = false, + this.isConfigurationValid = false, + this.isDataObscured = false, + }); + + BugseeConfigState copyWith({ + bool? isRestartRequired, + bool? isBugseeEnabled, + bool? isVideoCaptureEnabled, + bool? isConfigurationValid, + bool? isDataObscured, + }) => + BugseeConfigState( + isRestartRequired: isRestartRequired ?? this.isRestartRequired, + isBugseeEnabled: isBugseeEnabled ?? this.isBugseeEnabled, + isConfigurationValid: isConfigurationValid ?? this.isConfigurationValid, + isDataObscured: isDataObscured ?? this.isDataObscured, + isVideoCaptureEnabled: + isVideoCaptureEnabled ?? this.isVideoCaptureEnabled, + ); + + @override + List get props => [ + isRestartRequired, + isBugseeEnabled, + isVideoCaptureEnabled, + isConfigurationValid, + isDataObscured, + ]; +} diff --git a/src/app/lib/business/bugsee/bugsee_manager.dart b/src/app/lib/business/bugsee/bugsee_manager.dart index 0da89a77..afe4ff42 100644 --- a/src/app/lib/business/bugsee/bugsee_manager.dart +++ b/src/app/lib/business/bugsee/bugsee_manager.dart @@ -1,10 +1,11 @@ import 'dart:async'; import 'dart:io'; - import 'package:app/access/bugsee/bugsee_configuration_data.dart'; import 'package:app/access/bugsee/bugsee_repository.dart'; +import 'package:app/business/bugsee/bugsee_config_state.dart'; import 'package:bugsee_flutter/bugsee_flutter.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:logger/logger.dart'; import 'package:logger/web.dart'; @@ -18,25 +19,8 @@ abstract interface class BugseeManager { required BugseeRepository bugseeRepository, }) = _BugseeManager; - /// indicate if the app require a restart to reactivate the bugsee configurations - /// - /// `true` only if `isConfigurationValid == true` and bugsee is turned on - bool get isRestartRequired; - - /// indicate if bugsee is enabled or not - /// by default bugsee is enabled if `isConfigurationValid == true`. - bool get isBugseeEnabled; - - /// indicate whether video capturing is enabled or not. - /// enabled by default if `isBugseeEnabled == true`. - /// - /// cannot be true if `isBugseeEnabled == false`. - bool get isVideoCaptureEnabled; - - /// indicate if bugsee configuration is valid - /// config is valid if app in release mode and the provided token is valid - /// following the [bugseeTokenFormat] regex. - bool get isConfigurationValid; + /// Current BugseeManager state + BugseeConfigState get bugseeConfigState; /// initialize bugsee with given token /// bugsee is not available in debug mode @@ -61,10 +45,13 @@ abstract interface class BugseeManager { }); /// Manually update the current BugseeEnabled flag in shared prefs and in current manager singleton. - Future setIsBugseeEnabled(bool isBugseeEnabled); + Future setIsBugseeEnabled(bool value); + + /// Manually update isDataObscured flag in shared prefs and in current state [bugseeConfigState]. + Future setIsDataObscured(bool value); /// Manually update the current enableVideoCapture flag in shared prefs and in current manager singleton. - Future setIsVideoCaptureEnabled(bool isBugseeEnabled); + Future setIsVideoCaptureEnabled(bool value); /// Manually shows the built-in capture log report screen of Bugsee. Future showCaptureLogReport(); @@ -79,17 +66,11 @@ final class _BugseeManager implements BugseeManager { required this.bugseeRepository, }); - @override - bool isRestartRequired = false; - - @override - bool isBugseeEnabled = false; - - @override - late bool isVideoCaptureEnabled = false; + bool _initialDataObscuredState = false; + BugseeConfigState _currentState = BugseeConfigState(); @override - bool isConfigurationValid = true; + BugseeConfigState get bugseeConfigState => _currentState; late bool _isBugSeeInitialized; BugseeLaunchOptions? launchOptions; @@ -105,14 +86,18 @@ final class _BugseeManager implements BugseeManager { _isBugSeeInitialized = false; if (kDebugMode) { - isConfigurationValid = false; + _currentState = _currentState.copyWith( + isConfigurationValid: false, + ); logger.i("BUGSEE: deactivated in debug mode"); return; } if (bugseeToken == null || !RegExp(bugseeTokenFormat).hasMatch(bugseeToken)) { - isConfigurationValid = false; + _currentState = _currentState.copyWith( + isConfigurationValid: false, + ); logger.i( "BUGSEE: token is null or invalid, bugsee won't be initialized", ); @@ -123,9 +108,17 @@ final class _BugseeManager implements BugseeManager { await _launchBugseeLogger(bugseeToken); } - isBugseeEnabled = _isBugSeeInitialized; - isVideoCaptureEnabled = _isBugSeeInitialized && - (bugseeConfigurationData.isVideoCaptureEnabled ?? true); + var isDataObscured = bugseeConfigurationData.isDataObscrured ?? + bool.parse(dotenv.env['IS_DATA_OBSCURE'] ?? 'false'); + + _currentState = _currentState.copyWith( + isConfigurationValid: _isBugSeeInitialized, + isBugseeEnabled: _isBugSeeInitialized, + isVideoCaptureEnabled: _isBugSeeInitialized && + (bugseeConfigurationData.isVideoCaptureEnabled ?? true), + isDataObscured: isDataObscured, + ); + _initialDataObscuredState = isDataObscured; } Future _launchBugseeLogger(String bugseeToken) async { @@ -158,7 +151,7 @@ final class _BugseeManager implements BugseeManager { required Exception exception, StackTrace? stackTrace, }) async { - if (isBugseeEnabled) { + if (_currentState.isBugseeEnabled) { await Bugsee.logException(exception, stackTrace); } } @@ -168,21 +161,23 @@ final class _BugseeManager implements BugseeManager { required Exception exception, StackTrace? stackTrace, }) async { - if (isBugseeEnabled) { + if (_currentState.isBugseeEnabled) { await Bugsee.logUnhandledException(exception); } } @override Future setIsBugseeEnabled(bool value) async { - if (isConfigurationValid) { - isBugseeEnabled = value; - await bugseeRepository.setIsBugseeEnabled(isBugseeEnabled); - - isRestartRequired = _isBugSeeInitialized && isBugseeEnabled; - isVideoCaptureEnabled = isBugseeEnabled; + if (_currentState.isConfigurationValid) { + await bugseeRepository.setIsBugseeEnabled(value); + _currentState = _currentState.copyWith( + isBugseeEnabled: value, + isRestartRequired: value, + isVideoCaptureEnabled: value, + isDataObscured: value, + ); - if (!isRestartRequired) { + if (!_currentState.isRestartRequired) { await Bugsee.stop(); } } @@ -190,10 +185,14 @@ final class _BugseeManager implements BugseeManager { @override Future setIsVideoCaptureEnabled(bool value) async { - if (isBugseeEnabled) { - isVideoCaptureEnabled = value; - await bugseeRepository.setIsVideoCaptureEnabled(isVideoCaptureEnabled); - if (!isVideoCaptureEnabled) { + if (_currentState.isBugseeEnabled) { + _currentState = _currentState.copyWith( + isVideoCaptureEnabled: value, + ); + await bugseeRepository.setIsVideoCaptureEnabled( + _currentState.isVideoCaptureEnabled, + ); + if (!_currentState.isVideoCaptureEnabled) { await Bugsee.pause(); } else { await Bugsee.resume(); @@ -203,8 +202,19 @@ final class _BugseeManager implements BugseeManager { @override Future showCaptureLogReport() async { - if (isBugseeEnabled) { + if (_currentState.isBugseeEnabled) { await Bugsee.showReportDialog(); } } + + @override + Future setIsDataObscured(bool value) async { + if (_currentState.isBugseeEnabled) { + await bugseeRepository.setIsDataObscure(value); + _currentState = _currentState.copyWith( + isRestartRequired: value != _initialDataObscuredState, + isDataObscured: value, + ); + } + } } diff --git a/src/app/lib/presentation/dad_jokes/dad_joke_list_item.dart b/src/app/lib/presentation/dad_jokes/dad_joke_list_item.dart index 3c6bcd77..78811cb8 100644 --- a/src/app/lib/presentation/dad_jokes/dad_joke_list_item.dart +++ b/src/app/lib/presentation/dad_jokes/dad_joke_list_item.dart @@ -1,5 +1,7 @@ +import 'package:app/business/bugsee/bugsee_manager.dart'; import 'package:app/business/dad_jokes/dad_joke.dart'; import 'package:app/business/dad_jokes/dad_jokes_service.dart'; +import 'package:bugsee_flutter/bugsee_flutter.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; @@ -7,6 +9,7 @@ import 'package:get_it/get_it.dart'; final class DadJokeListItem extends StatelessWidget { /// The dad jokes service used to add or remove favorite. final _dadJokesService = GetIt.I(); + final _bugseeManager = GetIt.I(); /// The dad joke. final DadJoke dadJoke; @@ -17,8 +20,14 @@ final class DadJokeListItem extends StatelessWidget { Widget build(BuildContext context) { return Card( child: ListTile( - title: Text(dadJoke.title), - subtitle: Text(dadJoke.text), + title: BugseeSecureView( + enabled: _bugseeManager.bugseeConfigState.isDataObscured, + child: Text(dadJoke.title), + ), + subtitle: BugseeSecureView( + enabled: _bugseeManager.bugseeConfigState.isDataObscured, + child: Text(dadJoke.text), + ), trailing: dadJoke.isFavorite ? const Icon( Icons.favorite, diff --git a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart index 3952cbf5..a5018ff0 100644 --- a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart +++ b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart @@ -1,3 +1,4 @@ +import 'package:app/business/bugsee/bugsee_config_state.dart'; import 'package:app/business/bugsee/bugsee_manager.dart'; import 'package:app/presentation/diagnostic/diagnostic_button.dart'; import 'package:app/presentation/diagnostic/diagnostic_switch.dart'; @@ -16,16 +17,12 @@ class BugseeConfigurationWidget extends StatefulWidget { class _BugseeConfigurationWidgetState extends State { final BugseeManager bugseeManager = GetIt.I.get(); - late bool isConfigEnabled; - late bool isCaptureVideoEnabled; - late bool requireRestart; + late BugseeConfigState state; @override void initState() { super.initState(); - isConfigEnabled = bugseeManager.isBugseeEnabled; - isCaptureVideoEnabled = bugseeManager.isVideoCaptureEnabled; - requireRestart = bugseeManager.isRestartRequired; + state = bugseeManager.bugseeConfigState; } @override @@ -34,7 +31,7 @@ class _BugseeConfigurationWidgetState extends State { children: [ Column( children: [ - if (!bugseeManager.isConfigurationValid) + if (!bugseeManager.bugseeConfigState.isConfigurationValid) Container( color: const Color.fromARGB(170, 255, 0, 0), child: const Text( @@ -49,11 +46,11 @@ class _BugseeConfigurationWidgetState extends State { ), ), ), - if (requireRestart) + if (state.isRestartRequired) Container( color: const Color.fromARGB(170, 255, 0, 0), child: const Text( - "In order to reactivate Bugsee logger restart the app.", + "Bugsee configuration changed. Please restart the application to apply the changes.", style: TextStyle( color: Colors.white, fontSize: 20, @@ -64,23 +61,31 @@ class _BugseeConfigurationWidgetState extends State { ), DiagnosticSwitch( label: 'Bugsee enabled', - value: isConfigEnabled, + value: state.isBugseeEnabled, onChanged: (value) async { await bugseeManager.setIsBugseeEnabled(value); setState(() { - isConfigEnabled = bugseeManager.isBugseeEnabled; - isCaptureVideoEnabled = bugseeManager.isVideoCaptureEnabled; - requireRestart = bugseeManager.isRestartRequired; + state = bugseeManager.bugseeConfigState; }); }, ), DiagnosticSwitch( label: 'Video capture enabled', - value: isCaptureVideoEnabled, + value: state.isVideoCaptureEnabled, onChanged: (value) async { await bugseeManager.setIsVideoCaptureEnabled(value); setState(() { - isCaptureVideoEnabled = bugseeManager.isVideoCaptureEnabled; + state = bugseeManager.bugseeConfigState; + }); + }, + ), + DiagnosticSwitch( + label: 'Obscure data', + value: state.isDataObscured, + onChanged: (value) async { + await bugseeManager.setIsDataObscured(value); + setState(() { + state = bugseeManager.bugseeConfigState; }); }, ), From 621f58e3aa62d6f837b24db0dcaf73b05fae9a26 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Tue, 19 Nov 2024 12:46:06 +0100 Subject: [PATCH 02/14] feat: customize logs, traces and events reported to Bugsee dashboard --- src/app/.env.staging | 4 +- .../bugsee/bugsee_configuration_data.dart | 42 +++++++- .../lib/access/bugsee/bugsee_repository.dart | 47 +++++++- .../business/bugsee/bugsee_config_state.dart | 29 ++++- .../lib/business/bugsee/bugsee_manager.dart | 102 +++++++++++++++--- .../bugsee_configuration_widget.dart | 49 +++++++++ 6 files changed, 248 insertions(+), 25 deletions(-) diff --git a/src/app/.env.staging b/src/app/.env.staging index e549e665..dd8059ed 100644 --- a/src/app/.env.staging +++ b/src/app/.env.staging @@ -6,4 +6,6 @@ APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=1 DIAGNOSTIC_ENABLED=true -IS_DATA_OBSCURE=true \ No newline at end of file +IS_DATA_OBSCURE=true +DISABLE_LOG_COLLECTION=true +FILTER_LOG_COLLECTION=true \ No newline at end of file diff --git a/src/app/lib/access/bugsee/bugsee_configuration_data.dart b/src/app/lib/access/bugsee/bugsee_configuration_data.dart index b636a424..db424a8c 100644 --- a/src/app/lib/access/bugsee/bugsee_configuration_data.dart +++ b/src/app/lib/access/bugsee/bugsee_configuration_data.dart @@ -1,4 +1,6 @@ -final class BugseeConfigurationData { +import 'package:equatable/equatable.dart'; + +final class BugseeConfigurationData extends Equatable { /// Gets whether the Bugsee SDK is enabled or not. if [Null] it fallbacks to a new installed app so it will be enabled. final bool? isBugseeEnabled; @@ -6,11 +8,45 @@ final class BugseeConfigurationData { final bool? isVideoCaptureEnabled; /// Indicate whether bugsee obscure application data in videos and images or not. - final bool? isDataObscrured; + final bool? isDataObscured; + + /// Indicate whether logs are collected or not. + final bool? isLogCollectionEnabled; + + /// Indicate whether logs are filtred during reports or not. + final bool? isLogsFilterEnabled; const BugseeConfigurationData({ this.isBugseeEnabled, this.isVideoCaptureEnabled, - this.isDataObscrured, + this.isDataObscured, + this.isLogCollectionEnabled, + this.isLogsFilterEnabled, }); + + BugseeConfigurationData copyWith({ + bool? isBugseeEnabled, + bool? isVideoCaptureEnabled, + bool? isDataObscured, + bool? isLogCollectionEnabled, + bool? isLogsFilterEnabled, + }) => + BugseeConfigurationData( + isBugseeEnabled: isBugseeEnabled ?? this.isBugseeEnabled, + isVideoCaptureEnabled: + isVideoCaptureEnabled ?? this.isVideoCaptureEnabled, + isDataObscured: isDataObscured ?? this.isDataObscured, + isLogCollectionEnabled: + isLogCollectionEnabled ?? this.isLogCollectionEnabled, + isLogsFilterEnabled: isLogsFilterEnabled ?? this.isLogsFilterEnabled, + ); + + @override + List get props => [ + isBugseeEnabled, + isVideoCaptureEnabled, + isDataObscured, + isLogCollectionEnabled, + isLogsFilterEnabled, + ]; } diff --git a/src/app/lib/access/bugsee/bugsee_repository.dart b/src/app/lib/access/bugsee/bugsee_repository.dart index 74f32650..24519df5 100644 --- a/src/app/lib/access/bugsee/bugsee_repository.dart +++ b/src/app/lib/access/bugsee/bugsee_repository.dart @@ -16,12 +16,20 @@ abstract interface class BugseeRepository { /// Update whether data is obscure in shared prefs. Future setIsDataObscure(bool isDataObscure); + + /// Update whether [disableLogCollectionKey] flag. + Future setIsLogCollectionEnabled(bool isLogCollectionEnabled); + + /// Update whether [disableLogFilterKey] flag. + Future setIsLogFilterEnabled(bool isLogFilterEnabled); } final class _BugseeRepository implements BugseeRepository { final String _bugseeEnabledKey = 'bugseeEnabledKey'; final String _videoCaptureKey = 'videoCaptureKey'; final String _dataObscureKey = 'dataObscureKey'; + final String _disableLogCollectionKey = 'disableLogCollectionKey'; + final String _disableLogFilterKey = 'disableLogFilterKey'; @override Future getBugseeConfiguration() async { @@ -29,7 +37,10 @@ final class _BugseeRepository implements BugseeRepository { return BugseeConfigurationData( isBugseeEnabled: sharedPrefInstance.getBool(_bugseeEnabledKey), isVideoCaptureEnabled: sharedPrefInstance.getBool(_videoCaptureKey), - isDataObscrured: sharedPrefInstance.getBool(_dataObscureKey), + isDataObscured: sharedPrefInstance.getBool(_dataObscureKey), + isLogCollectionEnabled: + sharedPrefInstance.getBool(_disableLogCollectionKey), + isLogsFilterEnabled: sharedPrefInstance.getBool(_disableLogFilterKey), ); } @@ -80,4 +91,38 @@ final class _BugseeRepository implements BugseeRepository { ); } } + + @override + Future setIsLogCollectionEnabled(bool isLogCollected) async { + final sharedPrefInstance = await SharedPreferences.getInstance(); + + bool isSaved = await sharedPrefInstance.setBool( + _disableLogCollectionKey, + isLogCollected, + ); + + if (!isSaved) { + throw PersistenceException( + message: + 'Error while setting $_disableLogCollectionKey $isLogCollected', + ); + } + } + + @override + Future setIsLogFilterEnabled(bool isLogFilterEnabled) async { + final sharedPrefInstance = await SharedPreferences.getInstance(); + + bool isSaved = await sharedPrefInstance.setBool( + _disableLogFilterKey, + isLogFilterEnabled, + ); + + if (!isSaved) { + throw PersistenceException( + message: + 'Error while setting $_disableLogFilterKey $isLogFilterEnabled', + ); + } + } } diff --git a/src/app/lib/business/bugsee/bugsee_config_state.dart b/src/app/lib/business/bugsee/bugsee_config_state.dart index 24461222..1b4f6337 100644 --- a/src/app/lib/business/bugsee/bugsee_config_state.dart +++ b/src/app/lib/business/bugsee/bugsee_config_state.dart @@ -1,37 +1,51 @@ import 'package:equatable/equatable.dart'; final class BugseeConfigState extends Equatable { - /// indicate if the app require a restart to reactivate the bugsee configurations + /// Indicate if the app require a restart to reactivate the bugsee configurations /// /// `true` only if `isConfigurationValid == true` and bugsee is turned on bool isRestartRequired; - /// indicate if bugsee is enabled or not + /// Indicate if bugsee is enabled or not /// by default bugsee is enabled if `isConfigurationValid == true`. bool isBugseeEnabled; - /// indicate whether video capturing is enabled or not. + /// Indicate whether video capturing is enabled or not. /// enabled by default if `isBugseeEnabled == true`. /// /// cannot be true if `isBugseeEnabled == false`. bool isVideoCaptureEnabled; - /// indicate if bugsee configuration is valid + /// Indicate if bugsee configuration is valid /// config is valid if app in release mode and the provided token is valid /// following the [bugseeTokenFormat] regex. bool isConfigurationValid; - /// indicate whether data is obscured in report videos + /// Indicate whether data is obscured in report videos /// /// cannot be true if `isBugseeEnabled == false`. bool isDataObscured; + /// Indicate whether log will be collected during Bugsee reporting or not + /// by default logs are collected but filterd. + /// + /// This value is initialized from [dotenv.env] and shared prefs storage. + bool isLogCollectionEnabled; + + /// Indicate whether log will be filterd or not + /// by default all logs are filted using [bugseeFilterRegex] defined in [BugseeManager] + /// + /// This value is initialized from [dotenv.env] map and shared prefs storage. + bool isLogFilterEnabled; + BugseeConfigState({ this.isRestartRequired = false, this.isBugseeEnabled = false, this.isVideoCaptureEnabled = false, this.isConfigurationValid = false, this.isDataObscured = false, + this.isLogCollectionEnabled = false, + this.isLogFilterEnabled = false, }); BugseeConfigState copyWith({ @@ -40,12 +54,17 @@ final class BugseeConfigState extends Equatable { bool? isVideoCaptureEnabled, bool? isConfigurationValid, bool? isDataObscured, + bool? isLogCollectionEnabled, + bool? isLogFilterEnabled, }) => BugseeConfigState( isRestartRequired: isRestartRequired ?? this.isRestartRequired, isBugseeEnabled: isBugseeEnabled ?? this.isBugseeEnabled, isConfigurationValid: isConfigurationValid ?? this.isConfigurationValid, isDataObscured: isDataObscured ?? this.isDataObscured, + isLogFilterEnabled: isLogFilterEnabled ?? this.isLogFilterEnabled, + isLogCollectionEnabled: + isLogCollectionEnabled ?? this.isLogCollectionEnabled, isVideoCaptureEnabled: isVideoCaptureEnabled ?? this.isVideoCaptureEnabled, ); diff --git a/src/app/lib/business/bugsee/bugsee_manager.dart b/src/app/lib/business/bugsee/bugsee_manager.dart index afe4ff42..168617e7 100644 --- a/src/app/lib/business/bugsee/bugsee_manager.dart +++ b/src/app/lib/business/bugsee/bugsee_manager.dart @@ -11,8 +11,9 @@ import 'package:logger/web.dart'; const String bugseeTokenFormat = r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'; +const String bugseeFilterRegex = r'.'; -///Service related to initializing Bugsee service +/// Service related to initializing Bugsee service abstract interface class BugseeManager { factory BugseeManager({ required Logger logger, @@ -22,7 +23,7 @@ abstract interface class BugseeManager { /// Current BugseeManager state BugseeConfigState get bugseeConfigState; - /// initialize bugsee with given token + /// Initialize bugsee with given token /// bugsee is not available in debug mode /// * [bugseeToken]: nullable bugsee token, if null bugsee won't be initialized make sure you provide /// [BUGSEE_TOKEN] in the env using `--dart-define` or `launch.json` on vscode @@ -35,6 +36,8 @@ abstract interface class BugseeManager { Future logException({ required Exception exception, StackTrace? stackTrace, + Map? traces, + Map?>? events, }); /// Manually log an unhandled exception with a stack trace @@ -55,6 +58,12 @@ abstract interface class BugseeManager { /// Manually shows the built-in capture log report screen of Bugsee. Future showCaptureLogReport(); + + /// Manually update whether logs will be collected or not flag in shared prefs. + Future setIsLogsCollectionEnabled(bool value); + + /// Manually update isLogFilterEnabled flag in shared prefs. + Future setIsLogFilterEnabeld(bool value); } final class _BugseeManager implements BugseeManager { @@ -66,21 +75,32 @@ final class _BugseeManager implements BugseeManager { required this.bugseeRepository, }); - bool _initialDataObscuredState = false; BugseeConfigState _currentState = BugseeConfigState(); @override BugseeConfigState get bugseeConfigState => _currentState; late bool _isBugSeeInitialized; + late BugseeConfigurationData configurationData; BugseeLaunchOptions? launchOptions; @override Future initialize({ String? bugseeToken, }) async { - BugseeConfigurationData bugseeConfigurationData = - await bugseeRepository.getBugseeConfiguration(); + configurationData = await bugseeRepository.getBugseeConfiguration(); + configurationData = configurationData.copyWith( + isLogCollectionEnabled: configurationData.isLogCollectionEnabled ?? + bool.parse( + dotenv.env['DISABLE_LOG_COLLECTION'] ?? 'true', + ), + isLogsFilterEnabled: configurationData.isLogsFilterEnabled ?? + bool.parse( + dotenv.env['FILTER_LOG_COLLECTION'] ?? 'true', + ), + isDataObscured: configurationData.isDataObscured ?? + bool.parse(dotenv.env['IS_DATA_OBSCURE'] ?? 'true'), + ); launchOptions = _initializeLaunchOptions(); _isBugSeeInitialized = false; @@ -104,21 +124,22 @@ final class _BugseeManager implements BugseeManager { return; } - if (bugseeConfigurationData.isBugseeEnabled ?? true) { + _currentState = _currentState.copyWith( + isLogFilterEnabled: configurationData.isLogsFilterEnabled, + isLogCollectionEnabled: configurationData.isLogCollectionEnabled, + ); + + if (configurationData.isBugseeEnabled ?? true) { await _launchBugseeLogger(bugseeToken); } - var isDataObscured = bugseeConfigurationData.isDataObscrured ?? - bool.parse(dotenv.env['IS_DATA_OBSCURE'] ?? 'false'); - _currentState = _currentState.copyWith( isConfigurationValid: _isBugSeeInitialized, isBugseeEnabled: _isBugSeeInitialized, isVideoCaptureEnabled: _isBugSeeInitialized && - (bugseeConfigurationData.isVideoCaptureEnabled ?? true), - isDataObscured: isDataObscured, + (configurationData.isVideoCaptureEnabled ?? true), + isDataObscured: configurationData.isDataObscured, ); - _initialDataObscuredState = isDataObscured; } Future _launchBugseeLogger(String bugseeToken) async { @@ -135,23 +156,47 @@ final class _BugseeManager implements BugseeManager { }, launchOptions: launchOptions, ); + if (_currentState.isLogFilterEnabled) { + Bugsee.setLogFilter(_filterBugseeLogs); + } } BugseeLaunchOptions? _initializeLaunchOptions() { if (Platform.isAndroid) { - return AndroidLaunchOptions(); + return AndroidLaunchOptions() + ..captureLogs = (configurationData.isLogCollectionEnabled ?? true); } else if (Platform.isIOS) { - return IOSLaunchOptions(); + return IOSLaunchOptions() + ..captureLogs = (configurationData.isLogCollectionEnabled ?? true); } return null; } + Future _filterBugseeLogs(BugseeLogEvent logEvent) async { + logEvent.text = logEvent.text.replaceAll(RegExp(bugseeFilterRegex), ''); + return logEvent; + } + @override Future logException({ required Exception exception, StackTrace? stackTrace, + Map? traces, + Map?>? events, }) async { if (_currentState.isBugseeEnabled) { + if (traces != null) { + for (var trace in traces.entries) { + await Bugsee.trace(trace.key, trace.value); + } + } + + if (events != null) { + for (var event in events.entries) { + await Bugsee.event(event.key, event.value); + } + } + await Bugsee.logException(exception, stackTrace); } } @@ -212,9 +257,36 @@ final class _BugseeManager implements BugseeManager { if (_currentState.isBugseeEnabled) { await bugseeRepository.setIsDataObscure(value); _currentState = _currentState.copyWith( - isRestartRequired: value != _initialDataObscuredState, + isRestartRequired: value != configurationData.isDataObscured, isDataObscured: value, ); } } + + @override + Future setIsLogsCollectionEnabled(bool value) async { + if (_currentState.isBugseeEnabled) { + await bugseeRepository.setIsLogCollectionEnabled(value); + _currentState = _currentState.copyWith( + isRestartRequired: value != configurationData.isLogCollectionEnabled, + isLogCollectionEnabled: value, + ); + if (!value) { + _currentState = _currentState.copyWith( + isLogFilterEnabled: false, + ); + } + } + } + + @override + Future setIsLogFilterEnabeld(bool value) async { + if (_currentState.isBugseeEnabled && _currentState.isLogCollectionEnabled) { + await bugseeRepository.setIsLogFilterEnabled(value); + _currentState = _currentState.copyWith( + isRestartRequired: value != configurationData.isLogsFilterEnabled, + isLogFilterEnabled: value, + ); + } + } } diff --git a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart index a5018ff0..1a792997 100644 --- a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart +++ b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart @@ -1,5 +1,8 @@ +import 'dart:math'; + import 'package:app/business/bugsee/bugsee_config_state.dart'; import 'package:app/business/bugsee/bugsee_manager.dart'; +import 'package:app/business/dad_jokes/dad_jokes_service.dart'; import 'package:app/presentation/diagnostic/diagnostic_button.dart'; import 'package:app/presentation/diagnostic/diagnostic_switch.dart'; import 'package:flutter/foundation.dart'; @@ -89,6 +92,26 @@ class _BugseeConfigurationWidgetState extends State { }); }, ), + DiagnosticSwitch( + label: 'Enabling log collection', + value: state.isLogCollectionEnabled, + onChanged: (value) async { + await bugseeManager.setIsLogsCollectionEnabled(value); + setState(() { + state = bugseeManager.bugseeConfigState; + }); + }, + ), + DiagnosticSwitch( + label: 'Enable log filter', + value: state.isLogFilterEnabled, + onChanged: (value) async { + await bugseeManager.setIsLogFilterEnabeld(value); + setState(() { + state = bugseeManager.bugseeConfigState; + }); + }, + ), ], ), DiagnosticButton( @@ -103,6 +126,32 @@ class _BugseeConfigurationWidgetState extends State { bugseeManager.logUnhandledException(exception: Exception()); }, ), + DiagnosticButton( + label: 'Log traces', + onPressed: () { + bugseeManager.logException( + exception: Exception(), + traces: { + 'date': DateTime.now().millisecondsSinceEpoch, + 'id': Random().nextInt(20), + }, + ); + }, + ), + DiagnosticButton( + label: 'Log events', + onPressed: () { + bugseeManager.logException( + exception: Exception(), + events: { + 'data': { + 'date': DateTime.now().millisecondsSinceEpoch, + 'id': Random().nextInt(20), + }, + }, + ); + }, + ), DiagnosticButton( label: 'Show report dialog', onPressed: () { From 12f2d65433f6ec79e14707c5ecfdf10475409c24 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Thu, 21 Nov 2024 12:31:55 +0100 Subject: [PATCH 03/14] feat: implement global bugsee exception handler --- src/app/.env.staging | 2 +- .../lib/business/bugsee/bugsee_manager.dart | 118 ++++++++++-------- src/app/lib/main.dart | 25 ++-- .../dad_jokes/dad_joke_list_item.dart | 10 -- .../bugsee_configuration_widget.dart | 63 ++++------ 5 files changed, 104 insertions(+), 114 deletions(-) diff --git a/src/app/.env.staging b/src/app/.env.staging index ac9ee671..a6d71bd6 100644 --- a/src/app/.env.staging +++ b/src/app/.env.staging @@ -8,5 +8,5 @@ REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=1 DIAGNOSTIC_ENABLED=true IS_DATA_OBSCURE=true DISABLE_LOG_COLLECTION=true -FILTER_LOG_COLLECTION=true +FILTER_LOG_COLLECTION=false ATTACH_LOG_FILE=true \ No newline at end of file diff --git a/src/app/lib/business/bugsee/bugsee_manager.dart b/src/app/lib/business/bugsee/bugsee_manager.dart index 401f31de..3f6f5c2d 100644 --- a/src/app/lib/business/bugsee/bugsee_manager.dart +++ b/src/app/lib/business/bugsee/bugsee_manager.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:app/access/bugsee/bugsee_configuration_data.dart'; import 'package:app/access/bugsee/bugsee_repository.dart'; +import 'package:app/access/persistence_exception.dart'; import 'package:app/business/bugsee/bugsee_config_state.dart'; import 'package:app/business/logger/logger_manager.dart'; import 'package:bugsee_flutter/bugsee_flutter.dart'; @@ -16,15 +17,10 @@ const String bugseeFilterRegex = r'.'; /// Service related to initializing Bugsee service abstract interface class BugseeManager { - factory BugseeManager({ - required Logger logger, - required LoggerManager loggerManager, - required BugseeRepository bugseeRepository, - }) = _BugseeManager; + factory BugseeManager() = _BugseeManager; /// Current BugseeManager state BugseeConfigState get bugseeConfigState; - bool get onPressExceptionActivated; /// Initialize bugsee with given token /// bugsee is not available in debug mode @@ -32,6 +28,9 @@ abstract interface class BugseeManager { /// [BUGSEE_TOKEN] in the env using `--dart-define` or `launch.json` on vscode Future initialize({ String? bugseeToken, + required Logger logger, + required LoggerManager loggerManager, + required BugseeRepository bugseeRepository, }); /// Manually log a provided exception with a stack trace @@ -47,14 +46,6 @@ abstract interface class BugseeManager { StackTrace? stackTrace, Map? traces, Map?>? events, - Map? attributes, - }); - - /// Manually log an unhandled exception with a stack trace - /// (critical severity exception in Bugsee dashboard) - Future logUnhandledException({ - required Exception exception, - StackTrace? stackTrace, }); /// Manually update the current BugseeEnabled flag in shared prefs and in current manager singleton. @@ -75,29 +66,26 @@ abstract interface class BugseeManager { /// Manually update isLogFilterEnabled flag in shared prefs. Future setIsLogFilterEnabeld(bool value); - /// Manually update whether bugsee will catch exception when pressing - /// dadjoke item or not. - /// - /// By is set to `false` - void setOnPressExceptionActivated(bool value); - /// Manually update whether Bugsee attach the log file with the reported /// exception or not. /// /// By default the log file is attached Future setAttachLogFileEnabled(bool value); + + //TODO add documentation + Future inteceptor(Object error, StackTrace stackTrace); + Future addAttributes(Map attributes); + Future addEmailAttribute(String email); + Future clearEmailAttribute(); + Future clearAttribute(String attribute); } final class _BugseeManager implements BugseeManager { - final Logger logger; - final LoggerManager loggerManager; - final BugseeRepository bugseeRepository; - - _BugseeManager({ - required this.logger, - required this.loggerManager, - required this.bugseeRepository, - }); + late Logger logger; + late LoggerManager loggerManager; + late BugseeRepository bugseeRepository; + + _BugseeManager(); BugseeConfigState _currentState = const BugseeConfigState(); @@ -107,15 +95,19 @@ final class _BugseeManager implements BugseeManager { late bool _isBugSeeInitialized; late BugseeConfigurationData configurationData; - @override - bool onPressExceptionActivated = false; - BugseeLaunchOptions? launchOptions; @override Future initialize({ String? bugseeToken, + required Logger logger, + required LoggerManager loggerManager, + required BugseeRepository bugseeRepository, }) async { + this.logger = logger; + this.loggerManager = loggerManager; + this.bugseeRepository = bugseeRepository; + configurationData = await bugseeRepository.getBugseeConfiguration(); configurationData = configurationData.copyWith( isLogCollectionEnabled: configurationData.isLogCollectionEnabled ?? @@ -231,7 +223,6 @@ final class _BugseeManager implements BugseeManager { StackTrace? stackTrace, Map? traces, Map?>? events, - Map? attributes, }) async { if (_currentState.isBugseeEnabled) { if (traces != null) { @@ -246,26 +237,10 @@ final class _BugseeManager implements BugseeManager { } } - if (attributes != null) { - for (var attribute in attributes.entries) { - await Bugsee.setAttribute(attribute.key, attribute.value); - } - } - await Bugsee.logException(exception, stackTrace); } } - @override - Future logUnhandledException({ - required Exception exception, - StackTrace? stackTrace, - }) async { - if (_currentState.isBugseeEnabled) { - await Bugsee.logUnhandledException(exception); - } - } - @override Future setIsBugseeEnabled(bool value) async { if (_currentState.isConfigurationValid) { @@ -345,11 +320,6 @@ final class _BugseeManager implements BugseeManager { } } - @override - void setOnPressExceptionActivated(bool value) { - onPressExceptionActivated = value; - } - @override Future setAttachLogFileEnabled(bool value) async { if (_currentState.isBugseeEnabled) { @@ -360,4 +330,44 @@ final class _BugseeManager implements BugseeManager { ); } } + + @override + Future inteceptor( + Object error, + StackTrace stackTrace, + ) async { + String? message = switch (error.runtimeType) { + const (PersistenceException) => (error as PersistenceException).message, + _ => null, + }; + await logException( + exception: Exception(error), + stackTrace: stackTrace, + traces: { + 'message': message, + }, + ); + } + + @override + Future addAttributes(Map attributes) async { + for (var attribute in attributes.entries) { + await Bugsee.setAttribute(attribute.key, attribute.value); + } + } + + @override + Future clearAttribute(String attribute) async { + await Bugsee.clearAttribute(attribute); + } + + @override + Future addEmailAttribute(String email) async { + await Bugsee.setEmail(email); + } + + @override + Future clearEmailAttribute() async { + await Bugsee.clearEmail(); + } } diff --git a/src/app/lib/main.dart b/src/app/lib/main.dart index 2ad123b2..e1cc2497 100644 --- a/src/app/lib/main.dart +++ b/src/app/lib/main.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:alice/alice.dart'; @@ -36,8 +37,14 @@ import 'package:logger/logger.dart'; late Logger _logger; Future main() async { - await initializeComponents(); - runApp(const App()); + _initializeBugseeManager(); + runZonedGuarded( + () async { + await initializeComponents(); + runApp(const App()); + }, + GetIt.I.get().inteceptor, + ); } Future initializeComponents({bool? isMocked}) async { @@ -119,17 +126,19 @@ Future _registerAndLoadLoggers() async { GetIt.I.registerSingleton(_logger); } -Future _registerBugseeManager() async { +void _initializeBugseeManager() { GetIt.I.registerSingleton(BugseeRepository()); GetIt.I.registerSingleton( - BugseeManager( - logger: GetIt.I.get(), - loggerManager: GetIt.I.get(), - bugseeRepository: GetIt.I.get(), - ), + BugseeManager(), ); +} + +Future _registerBugseeManager() async { GetIt.I.get().initialize( bugseeToken: const String.fromEnvironment('BUGSEE_TOKEN'), + logger: GetIt.I.get(), + loggerManager: GetIt.I.get(), + bugseeRepository: GetIt.I.get(), ); } diff --git a/src/app/lib/presentation/dad_jokes/dad_joke_list_item.dart b/src/app/lib/presentation/dad_jokes/dad_joke_list_item.dart index daeb1173..78811cb8 100644 --- a/src/app/lib/presentation/dad_jokes/dad_joke_list_item.dart +++ b/src/app/lib/presentation/dad_jokes/dad_joke_list_item.dart @@ -39,16 +39,6 @@ final class DadJokeListItem extends StatelessWidget { titleAlignment: ListTileTitleAlignment.top, contentPadding: const EdgeInsets.all(16), onTap: () async { - if (_bugseeManager.onPressExceptionActivated) { - _bugseeManager.logException( - exception: Exception(), - attributes: { - 'id': dadJoke.id, - 'title': dadJoke.title, - }, - ); - } - if (dadJoke.isFavorite) { await _dadJokesService.removeFavoriteDadJoke(dadJoke); } else { diff --git a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart index 51d670e6..50096310 100644 --- a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart +++ b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart @@ -20,13 +20,11 @@ class _BugseeConfigurationWidgetState extends State { final BugseeManager bugseeManager = GetIt.I.get(); late BugseeConfigState state; - bool onPressItemLogException = false; @override void initState() { super.initState(); state = bugseeManager.bugseeConfigState; - onPressItemLogException = bugseeManager.onPressExceptionActivated; } @override @@ -113,17 +111,6 @@ class _BugseeConfigurationWidgetState extends State { }); }, ), - DiagnosticSwitch( - label: 'Log exception on pressing dad joke item', - value: onPressItemLogException, - onChanged: (value) async { - bugseeManager.setOnPressExceptionActivated(value); - setState(() { - onPressItemLogException = - bugseeManager.onPressExceptionActivated; - }); - }, - ), DiagnosticSwitch( label: 'Attach log file', value: state.attachLogFile, @@ -142,24 +129,6 @@ class _BugseeConfigurationWidgetState extends State { bugseeManager.logException(exception: Exception()); }, ), - DiagnosticButton( - label: 'Log an unhandled exception', - onPressed: () { - bugseeManager.logUnhandledException(exception: Exception()); - }, - ), - DiagnosticButton( - label: 'Log Exception with traces', - onPressed: () { - bugseeManager.logException( - exception: Exception(), - traces: { - 'date': DateTime.now().millisecondsSinceEpoch, - 'id': Random().nextInt(20), - }, - ); - }, - ), DiagnosticButton( label: 'Log Exception with events', onPressed: () { @@ -175,17 +144,29 @@ class _BugseeConfigurationWidgetState extends State { }, ), DiagnosticButton( - label: 'Delete all attribute', + label: 'Add email attributes', onPressed: () { - bugseeManager.logException( - exception: Exception(), - events: { - 'data': { - 'date': DateTime.now().millisecondsSinceEpoch, - 'id': Random().nextInt(20), - }, - }, - ); + bugseeManager.addEmailAttribute('john.doe@nventive.com'); + }, + ), + DiagnosticButton( + label: 'Add name attribute', + onPressed: () { + bugseeManager.addAttributes({ + 'name': 'John Doe', + }); + }, + ), + DiagnosticButton( + label: 'Clear email attribute', + onPressed: () { + bugseeManager.clearEmailAttribute(); + }, + ), + DiagnosticButton( + label: 'Clear name attribute', + onPressed: () { + bugseeManager.clearAttribute('name'); }, ), DiagnosticButton( From 0f2749144b637c6c98b8203c5a56721e6d97fee4 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Thu, 21 Nov 2024 12:48:12 +0100 Subject: [PATCH 04/14] chore: add flutter framework rendering error interceptor handler --- .../bugsee/bugsee_configuration_data.dart | 1 + .../bugsee/bugsee_default_configuration.dart | 53 ------------------- .../lib/business/bugsee/bugsee_manager.dart | 34 ++++++++++-- src/app/lib/main.dart | 4 +- 4 files changed, 34 insertions(+), 58 deletions(-) delete mode 100644 src/app/lib/access/bugsee/bugsee_default_configuration.dart diff --git a/src/app/lib/access/bugsee/bugsee_configuration_data.dart b/src/app/lib/access/bugsee/bugsee_configuration_data.dart index 30ecd3dd..5300dbac 100644 --- a/src/app/lib/access/bugsee/bugsee_configuration_data.dart +++ b/src/app/lib/access/bugsee/bugsee_configuration_data.dart @@ -16,6 +16,7 @@ final class BugseeConfigurationData extends Equatable { /// Indicate whether logs are filtred during reports or not. final bool? isLogsFilterEnabled; + /// Indicate whether attaching file in the Bugsee report is enabled or not final bool? attachLogFileEnabled; const BugseeConfigurationData({ diff --git a/src/app/lib/access/bugsee/bugsee_default_configuration.dart b/src/app/lib/access/bugsee/bugsee_default_configuration.dart deleted file mode 100644 index e10eaf02..00000000 --- a/src/app/lib/access/bugsee/bugsee_default_configuration.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:bugsee_flutter/bugsee_flutter.dart'; - -AndroidLaunchOptions androidLaunchOptions = AndroidLaunchOptions() - ..captureDeviceAndNetworkNames = false - ..captureLogs = false - ..crashReport = false - ..defaultBugPriority = BugseeSeverityLevel.blocker - ..defaultCrashPriority = BugseeSeverityLevel.critical - ..frameRate = BugseeFrameRate.low - ..maxDataSize = 20 - ..maxNetworkBodySize = 20 - ..maxRecordingTime = 20 - ..monitorNetwork = false - ..notificationBarTrigger = false - ..reportDescriptionRequired = false - ..reportEmailRequired = false - ..reportLabelsEnabled = false - ..reportLabelsRequired = false - ..reportPrioritySelector = false - ..reportSummaryRequired = false - ..screenshotEnabled = false - ..serviceMode = false - ..shakeToReport = false - ..videoEnabled = false - ..videoMode = BugseeVideoMode.v1 - ..videoQuality = BugseeVideoQuality.defaultQuality - ..videoScale = 0.5 - ..viewHierarchyEnabled = false - ..wifiOnlyUpload = true; - -IOSLaunchOptions iOSLaunchOptions = IOSLaunchOptions() - ..captureDeviceAndNetworkNames = false - ..captureLogs = false - ..crashReport = false - ..defaultBugPriority = BugseeSeverityLevel.blocker - ..defaultCrashPriority = BugseeSeverityLevel.critical - ..frameRate = BugseeFrameRate.low - ..maxDataSize = 20 - ..maxNetworkBodySize = 20 - ..maxRecordingTime = 20 - ..monitorNetwork = false - ..reportDescriptionRequired = false - ..reportEmailRequired = false - ..reportLabelsEnabled = false - ..reportLabelsRequired = false - ..reportPrioritySelector = false - ..reportSummaryRequired = false - ..screenshotEnabled = false - ..shakeToReport = false - ..videoEnabled = false - ..videoScale = 0.5 - ..viewHierarchyEnabled = false - ..wifiOnlyUpload = true; diff --git a/src/app/lib/business/bugsee/bugsee_manager.dart b/src/app/lib/business/bugsee/bugsee_manager.dart index 3f6f5c2d..7393b6b2 100644 --- a/src/app/lib/business/bugsee/bugsee_manager.dart +++ b/src/app/lib/business/bugsee/bugsee_manager.dart @@ -40,7 +40,6 @@ abstract interface class BugseeManager { ///* stackTrace: the strack trace of the exception, by default it's null ///* traces: the traces that led to the exception. ///* events: the events where the exception has been caught. - ///* attributes: data related to the exception. Future logException({ required Exception exception, StackTrace? stackTrace, @@ -72,11 +71,30 @@ abstract interface class BugseeManager { /// By default the log file is attached Future setAttachLogFileEnabled(bool value); - //TODO add documentation - Future inteceptor(Object error, StackTrace stackTrace); + /// Intecept all unhandled exception thrown by the dart framework + Future inteceptExceptions(Object error, StackTrace stackTrace); + + /// Intercept all unhandled rending exception thrown by the Flutter framework + Future inteceptRenderExceptions(FlutterErrorDetails error); + + /// Manually add a map of attributes + /// - the map entry key is the attribute name + /// - the map entry value is the attribute value (string, int, boolean) + /// + /// Attributes will be attached to all reported exception unless app is uninstalled + /// or attributes are removed manually Future addAttributes(Map attributes); + + /// Manually add an email attached to all reported. + /// + /// The email will be attached to all reported exception unless app is uninstalled + /// or the email is removed manually. Future addEmailAttribute(String email); + + /// Manually remove the email attribute attached using [addEmailAttribute] Future clearEmailAttribute(); + + /// Manually remove an attribute by the given key attached using [addAttributes] Future clearAttribute(String attribute); } @@ -332,7 +350,7 @@ final class _BugseeManager implements BugseeManager { } @override - Future inteceptor( + Future inteceptExceptions( Object error, StackTrace stackTrace, ) async { @@ -370,4 +388,12 @@ final class _BugseeManager implements BugseeManager { Future clearEmailAttribute() async { await Bugsee.clearEmail(); } + + @override + Future inteceptRenderExceptions(FlutterErrorDetails error) async { + await logException( + exception: Exception(error.exception), + stackTrace: error.stack, + ); + } } diff --git a/src/app/lib/main.dart b/src/app/lib/main.dart index e1cc2497..60788ae0 100644 --- a/src/app/lib/main.dart +++ b/src/app/lib/main.dart @@ -40,10 +40,12 @@ Future main() async { _initializeBugseeManager(); runZonedGuarded( () async { + FlutterError.onError = + GetIt.I.get().inteceptRenderExceptions; await initializeComponents(); runApp(const App()); }, - GetIt.I.get().inteceptor, + GetIt.I.get().inteceptExceptions, ); } From 7f20a67a2e8ae9791569a2b15ded8a9af646cd53 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Thu, 21 Nov 2024 15:55:25 +0100 Subject: [PATCH 05/14] docs: update project changelog --- src/cli/CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cli/CHANGELOG.md b/src/cli/CHANGELOG.md index a5ec128b..01b8b9c3 100644 --- a/src/cli/CHANGELOG.md +++ b/src/cli/CHANGELOG.md @@ -5,6 +5,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) Prefix your items with `(Template)` if the change is about the template and not the resulting application. +## 0.22.0 +- Add bugsee global inteceptor for dart exceptions to template app +- Add bugsee global inteceptor for flutter layout exceptions to template app +- Add bugsee custom attributes logging +- Add bugsee custom events logging +- Add bugsee custom traces logging +- Implement log file attach to reported bugsee exceptions +- Implement obsucre data feature from reported videos +- Implement Bugsee enable log and log filter features +- Update diagnostic overlay to test Bugsee advanced features + + ## 0.21.0 - Add bugsee sdk in Fluttter template - Update build stage in `steps-build-android.yml` and `steps-build-ios` providing bugsee token From ac885a059d679a924f0baf3262eefcd4bc0e1e56 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Fri, 22 Nov 2024 10:21:26 +0100 Subject: [PATCH 06/14] chore: update bugsee manager singleton registration in main file --- src/app/lib/main.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/lib/main.dart b/src/app/lib/main.dart index 60788ae0..27b064e8 100644 --- a/src/app/lib/main.dart +++ b/src/app/lib/main.dart @@ -136,6 +136,9 @@ void _initializeBugseeManager() { } Future _registerBugseeManager() async { + if (!GetIt.I.isRegistered()) { + _initializeBugseeManager(); + } GetIt.I.get().initialize( bugseeToken: const String.fromEnvironment('BUGSEE_TOKEN'), logger: GetIt.I.get(), From f6a69386f59670a392bd022b21cd04ec08f2803a Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Fri, 22 Nov 2024 12:02:51 +0100 Subject: [PATCH 07/14] chore: add mock attribute to bugsee manager initialization --- src/app/integration_test/bugsee_test.dart | 9 ++++++ .../integration_test/integration_test.dart | 7 +++++ .../lib/business/bugsee/bugsee_manager.dart | 31 ++++++++++++------- src/app/lib/main.dart | 8 +++-- 4 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 src/app/integration_test/bugsee_test.dart diff --git a/src/app/integration_test/bugsee_test.dart b/src/app/integration_test/bugsee_test.dart new file mode 100644 index 00000000..bb2c4c2f --- /dev/null +++ b/src/app/integration_test/bugsee_test.dart @@ -0,0 +1,9 @@ +import 'package:flutter_test/flutter_test.dart'; + +/// Test all Bugsee setup features +Future bugseeSetupTest() async { + testWidgets( + 'Test Bugsee configuration', + (tester) async {}, + ); +} diff --git a/src/app/integration_test/integration_test.dart b/src/app/integration_test/integration_test.dart index 5e555c07..18187bc0 100644 --- a/src/app/integration_test/integration_test.dart +++ b/src/app/integration_test/integration_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:integration_test/integration_test.dart'; +import 'bugsee_test.dart'; import 'dad_jokes_page_test.dart'; import 'forced_update_test.dart'; import 'kill_switch_test.dart'; @@ -12,6 +13,11 @@ import 'kill_switch_test.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); await initializeComponents(isMocked: true); + await registerBugseeManager( + isMock: true, + //A mock hexadecimal-based Bugsee token + bugseeToken: '01234567-0123-0123-0123-0123456789AB', + ); tearDownAll( () async => await GetIt.I.get().setMocking(false), @@ -20,4 +26,5 @@ Future main() async { await dadJokeTest(); await killSwitchTest(); await forcedUpdateTest(); + await bugseeSetupTest(); } diff --git a/src/app/lib/business/bugsee/bugsee_manager.dart b/src/app/lib/business/bugsee/bugsee_manager.dart index 7393b6b2..e9cd9e96 100644 --- a/src/app/lib/business/bugsee/bugsee_manager.dart +++ b/src/app/lib/business/bugsee/bugsee_manager.dart @@ -28,6 +28,7 @@ abstract interface class BugseeManager { /// [BUGSEE_TOKEN] in the env using `--dart-define` or `launch.json` on vscode Future initialize({ String? bugseeToken, + bool isMock, required Logger logger, required LoggerManager loggerManager, required BugseeRepository bugseeRepository, @@ -118,6 +119,7 @@ final class _BugseeManager implements BugseeManager { @override Future initialize({ String? bugseeToken, + bool isMock = false, required Logger logger, required LoggerManager loggerManager, required BugseeRepository bugseeRepository, @@ -145,6 +147,11 @@ final class _BugseeManager implements BugseeManager { launchOptions = _initializeLaunchOptions(); _isBugSeeInitialized = false; + if (isMock) { + _initializeBugsee(bugseeToken ?? ''); + return; + } + if (kDebugMode) { _currentState = _currentState.copyWith( isConfigurationValid: false, @@ -163,15 +170,12 @@ final class _BugseeManager implements BugseeManager { ); return; } + _initializeBugsee(bugseeToken); + } - _currentState = _currentState.copyWith( - isLogFilterEnabled: configurationData.isLogsFilterEnabled, - isLogCollectionEnabled: configurationData.isLogCollectionEnabled, - attachLogFile: configurationData.attachLogFileEnabled, - ); - + void _initializeBugsee(String bugseeToken) async { if (configurationData.isBugseeEnabled ?? true) { - await _launchBugseeLogger(bugseeToken); + _isBugSeeInitialized = await _launchBugseeLogger(bugseeToken); } _currentState = _currentState.copyWith( @@ -180,10 +184,14 @@ final class _BugseeManager implements BugseeManager { isVideoCaptureEnabled: _isBugSeeInitialized && (configurationData.isVideoCaptureEnabled ?? true), isDataObscured: configurationData.isDataObscured, + isLogFilterEnabled: configurationData.isLogsFilterEnabled, + isLogCollectionEnabled: configurationData.isLogCollectionEnabled, + attachLogFile: configurationData.attachLogFileEnabled, ); } - Future _launchBugseeLogger(String bugseeToken) async { + Future _launchBugseeLogger(String bugseeToken) async { + bool isInitialized = false; HttpOverrides.global = Bugsee.defaultHttpOverrides; await Bugsee.launch( bugseeToken, @@ -193,16 +201,17 @@ final class _BugseeManager implements BugseeManager { "BUGSEE: not initialized, verify bugsee token configuration", ); } - _isBugSeeInitialized = isBugseeLaunched; + isInitialized = isBugseeLaunched; }, launchOptions: launchOptions, ); - if (_currentState.isLogFilterEnabled) { + if (configurationData.isLogsFilterEnabled ?? false) { Bugsee.setLogFilter(_filterBugseeLogs); } - if (_currentState.attachLogFile) { + if (configurationData.attachLogFileEnabled ?? false) { Bugsee.setAttachmentsCallback(_attachLogFile); } + return isInitialized; } BugseeLaunchOptions? _initializeLaunchOptions() { diff --git a/src/app/lib/main.dart b/src/app/lib/main.dart index 27b064e8..770a6cb9 100644 --- a/src/app/lib/main.dart +++ b/src/app/lib/main.dart @@ -43,6 +43,7 @@ Future main() async { FlutterError.onError = GetIt.I.get().inteceptRenderExceptions; await initializeComponents(); + await registerBugseeManager(); runApp(const App()); }, GetIt.I.get().inteceptExceptions, @@ -53,7 +54,6 @@ Future initializeComponents({bool? isMocked}) async { WidgetsFlutterBinding.ensureInitialized(); await _registerAndLoadEnvironment(); await _registerAndLoadLoggers(); - await _registerBugseeManager(); _logger.d("Initialized environment and logger."); @@ -135,15 +135,17 @@ void _initializeBugseeManager() { ); } -Future _registerBugseeManager() async { +Future registerBugseeManager({bool? isMock, String? bugseeToken}) async { if (!GetIt.I.isRegistered()) { _initializeBugseeManager(); } GetIt.I.get().initialize( - bugseeToken: const String.fromEnvironment('BUGSEE_TOKEN'), + bugseeToken: + bugseeToken ?? const String.fromEnvironment('BUGSEE_TOKEN'), logger: GetIt.I.get(), loggerManager: GetIt.I.get(), bugseeRepository: GetIt.I.get(), + isMock: isMock ?? false, ); } From 99060203a1c8113ce71798e46f70f1d4355794f7 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Fri, 22 Nov 2024 14:35:56 +0100 Subject: [PATCH 08/14] chore: add platform check when intializing Bugsee --- .../business/bugsee/bugsee_config_state.dart | 17 +++++++++++++++++ src/app/lib/business/bugsee/bugsee_manager.dart | 15 +++++++++++++-- .../diagnostic/bugsee_configuration_widget.dart | 11 ++++------- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/app/lib/business/bugsee/bugsee_config_state.dart b/src/app/lib/business/bugsee/bugsee_config_state.dart index 1ede838a..8276b309 100644 --- a/src/app/lib/business/bugsee/bugsee_config_state.dart +++ b/src/app/lib/business/bugsee/bugsee_config_state.dart @@ -1,5 +1,16 @@ import 'package:equatable/equatable.dart'; +enum ConfigErrorEnum { + invalidReleaseMode(error: 'Bugsee is disabled in debug mode'), + invalidToken(error: 'Invalid token, cannot start Bugsee reporting'), + invalidPlatform(error: 'Bugsee cannot be configured on this platform'); + + final String error; + const ConfigErrorEnum({ + required this.error, + }); +} + final class BugseeConfigState extends Equatable { /// Indicate if the app require a restart to reactivate the bugsee configurations /// @@ -45,6 +56,9 @@ final class BugseeConfigState extends Equatable { /// By default it's enabled. final bool attachLogFile; + /// Indicate the configuration error type (debug, invalid token or invalid platform) + final ConfigErrorEnum? configErrorEnum; + const BugseeConfigState({ this.isRestartRequired = false, this.isBugseeEnabled = false, @@ -54,6 +68,7 @@ final class BugseeConfigState extends Equatable { this.isLogCollectionEnabled = false, this.isLogFilterEnabled = false, this.attachLogFile = false, + this.configErrorEnum, }); BugseeConfigState copyWith({ @@ -65,6 +80,7 @@ final class BugseeConfigState extends Equatable { bool? isLogCollectionEnabled, bool? isLogFilterEnabled, bool? attachLogFile, + ConfigErrorEnum? configErrorEnum, }) => BugseeConfigState( isRestartRequired: isRestartRequired ?? this.isRestartRequired, @@ -77,6 +93,7 @@ final class BugseeConfigState extends Equatable { isLogCollectionEnabled ?? this.isLogCollectionEnabled, isVideoCaptureEnabled: isVideoCaptureEnabled ?? this.isVideoCaptureEnabled, + configErrorEnum: configErrorEnum ?? this.configErrorEnum, ); @override diff --git a/src/app/lib/business/bugsee/bugsee_manager.dart b/src/app/lib/business/bugsee/bugsee_manager.dart index e9cd9e96..7748f18f 100644 --- a/src/app/lib/business/bugsee/bugsee_manager.dart +++ b/src/app/lib/business/bugsee/bugsee_manager.dart @@ -128,6 +128,15 @@ final class _BugseeManager implements BugseeManager { this.loggerManager = loggerManager; this.bugseeRepository = bugseeRepository; + if (!Platform.isIOS && !Platform.isAndroid) { + _currentState = _currentState.copyWith( + isConfigurationValid: false, + configErrorEnum: ConfigErrorEnum.invalidPlatform, + ); + logger.i("BUGSEE: ${_currentState.configErrorEnum?.error}"); + return; + } + configurationData = await bugseeRepository.getBugseeConfiguration(); configurationData = configurationData.copyWith( isLogCollectionEnabled: configurationData.isLogCollectionEnabled ?? @@ -155,8 +164,9 @@ final class _BugseeManager implements BugseeManager { if (kDebugMode) { _currentState = _currentState.copyWith( isConfigurationValid: false, + configErrorEnum: ConfigErrorEnum.invalidReleaseMode, ); - logger.i("BUGSEE: deactivated in debug mode"); + logger.i("BUGSEE: ${_currentState.configErrorEnum?.error}"); return; } @@ -164,9 +174,10 @@ final class _BugseeManager implements BugseeManager { !RegExp(bugseeTokenFormat).hasMatch(bugseeToken)) { _currentState = _currentState.copyWith( isConfigurationValid: false, + configErrorEnum: ConfigErrorEnum.invalidToken, ); logger.i( - "BUGSEE: token is null or invalid, bugsee won't be initialized", + "BUGSEE: ${_currentState.configErrorEnum?.error}", ); return; } diff --git a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart index 50096310..7d048bb1 100644 --- a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart +++ b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart @@ -4,7 +4,6 @@ import 'package:app/business/bugsee/bugsee_config_state.dart'; import 'package:app/business/bugsee/bugsee_manager.dart'; import 'package:app/presentation/diagnostic/diagnostic_button.dart'; import 'package:app/presentation/diagnostic/diagnostic_switch.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; @@ -33,14 +32,12 @@ class _BugseeConfigurationWidgetState extends State { children: [ Column( children: [ - if (!bugseeManager.bugseeConfigState.isConfigurationValid) + if (!state.isConfigurationValid) Container( color: const Color.fromARGB(170, 255, 0, 0), - child: const Text( - kDebugMode - ? "Bugsee is disabled in debug mode." - : "Invalid Bugsee token, capturing exceptions could not start", - style: TextStyle( + child: Text( + state.configErrorEnum?.error ?? '', + style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, From e7cb67879709786f2c3281be28f3c89544435c4b Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Thu, 28 Nov 2024 12:34:25 +0100 Subject: [PATCH 09/14] docs: add Bugsee implementation documentation --- doc/Bugsee.md | 141 ++++++++++++++++++ src/app/.env.dev | 4 +- src/app/.env.prod | 4 +- src/app/.env.staging | 8 +- .../bugsee/bugsee_configuration_data.dart | 10 +- .../lib/access/bugsee/bugsee_repository.dart | 44 +++--- .../lib/business/bugsee/bugsee_manager.dart | 30 ++-- 7 files changed, 187 insertions(+), 54 deletions(-) create mode 100644 doc/Bugsee.md diff --git a/doc/Bugsee.md b/doc/Bugsee.md new file mode 100644 index 00000000..b174b027 --- /dev/null +++ b/doc/Bugsee.md @@ -0,0 +1,141 @@ +# Bugsee + +This project include [Bugsee](https://www.bugsee.com/) reporting and Logging, for both Android and iOS apps. +Bugsee lets you monitor and get instant log of unhandled exceptions with traces, events, stacktrace and videos/screenshots of the reported exception. More features are provided by Bugsee like data obscuration and log filter. + +For this implementation we've used [bugsee_flutter](https://pub.dev/packages/bugsee_flutter) package. + +- By default only release apps will have Bugsee reporting enabled, to enable Bugsee in debug mode add your token in `launch.json` add remove the check on debug mode in `BugseeManager` class. +- **Token** + - Generate your token in order to test Bugsee logging and reporting + - Head to [Bugsee dashboard](https://www.bugsee.com/) + - Create a new account + - Create a new Android/iOS (choose Flutter framework) + - Copy-paste the generated token into `launch.json` + + +## 1) Features + +In this project we've implemented the following features of Bugsee: +- [Manual invocation](https://docs.bugsee.com/sdk/flutter/manual/) helpfull for developers to test their Bugsee integration and setup with new tokens. +- [Custom data](https://docs.bugsee.com/sdk/flutter/custom/) custom data could be attached to the logged exceptions (emails or other type of data) + - [Email](https://docs.bugsee.com/sdk/flutter/custom/#:~:text=Adding%20custom%20data-,User%20email,-When%20you%20already) this will identify the user whom experienced the exception/bug this will update the exception dashboard item from anonymous to the user's mail this data will be reported with every logged exception unless the app is deleted or removed manually. + - [Attributes](https://docs.bugsee.com/sdk/flutter/custom/#:~:text=User/Session%20attributes) attributes related to the user info, will be reported with every logged exception unless the app is deleted or removed manually. + - [Traces](https://docs.bugsee.com/sdk/flutter/custom/#:~:text=them%0ABugsee.clearAllAttributes()%3B-,Custom%20traces,-Traces%20may%20be) helpfull when developer want to track the change of specific value before the logging of the exception. + - [Events](https://docs.bugsee.com/sdk/flutter/custom/#:~:text=Custom%20events-,Events,-are%20identified%20by) highlight on which event the exception is logged, accept also json data attached to it. +- [Exception Logging](https://docs.bugsee.com/sdk/flutter/logs/) the app automatically log every unhandled exception: Dart SDK exception are related to data or logic errors and Flutter SDK errors that are related to layout and rendering issues. The implementation also offer an API to manually log an exception with traces and events. +- [Video Capturing](https://docs.bugsee.com/sdk/flutter/privacy/video/) video capturing is by default enabled in this project, but it can be turned off using the `videoEnabled` flag in the launchOptions object for Android and iOS. +- [Log reporting](https://docs.bugsee.com/sdk/flutter/privacy/logs/) all logs are filtered by default using the `_filterBugseeLogs` method, this can be turned off from the app or by removing the call to `setLogFilter` Bugsee method. +- [Obscure Data](https://docs.bugsee.com/sdk/flutter/privacy/video/#:~:text=Protecting%20flutter%20views): data obscuration is by default enabled in the project in order to protect user-related data from being leaked through captured videos. + +**Default configurations:** +Data obscuration, log collection, log filter and attaching log file features are initialized from the `.env.staging` file. +``` +BUGSEE_IS_DATA_OBSCURE=true +BUGSEE_DISABLE_LOG_COLLECTION=true +BUGSEE_FILTER_LOG_COLLECTION=false +BUGSEE_ATTACH_LOG_FILE=true +``` + +## 2) Implementation +- [Bugsee Manager](../src/app/lib/business/bugsee/bugsee_manager.dart): a service class that handle the Bugsee intialization, capturing logs, customize Bugsee fields and features (Video capture, data obscure, logs filter...) . +- [Bugsee Config State](../src/app/lib/business/bugsee/bugsee_config_state.dart): a state class holds all the actual Bugsee features status (whether enabled or not). +- [Bugsee Repository](../src/app/lib/access/bugsee/bugsee_repository.dart): save and retrieve Bugsee configuration from the shared preference storage. +- [Bugsee saved configuration](../src/app/lib/access/bugsee/bugsee_configuration_data.dart): holds the Bugsee saved configuration in shared preference, used in [Bugsee Manager](../src/app/lib/business/bugsee/bugsee_manager.dart) to initialize [Bugsee Config State](../src/app/lib/business/bugsee/bugsee_config_state.dart). + +### Intecepting exceptions +Bugsee implementation in this project, by default intecepts all the unhandled Dart and Flutter exception. + +`main.dart` +```dart +runZonedGuarded( + () async { + FlutterError.onError = + GetIt.I.get().inteceptRenderExceptions; + await initializeComponents(); + await registerBugseeManager(); + runApp(const App()); + }, + GetIt.I.get().inteceptExceptions, + ); +``` +the `inteceptExceptions` and `inteceptRenderExceptions` recerespectively report Dart and Flutter exceptions to the Bugsee Dashboard. + +`inteceptExceptions` +```dart +@override + Future inteceptExceptions( + Object error, + StackTrace stackTrace, + ) async { + String? message = switch (error.runtimeType) { + const (PersistenceException) => (error as PersistenceException).message, + _ => null, + }; + await logException( + exception: Exception(error), + stackTrace: stackTrace, + traces: { + 'message': message, + }, + ); + } +``` + +`inteceptRenderExceptions` +```dart +@override + Future inteceptRenderExceptions(FlutterErrorDetails error) async { + await logException( + exception: Exception(error.exception), + stackTrace: error.stack, + ); + } +``` + +### Manually invoke report dialog + +To manually display the report dialog, you call the `showCaptureLogReport` method from the `BugseeManager` class. + +```dart +final bugseeManager = Get.I.get(); +bugseeManger.showCaptureLogReport(); +``` + + +### Manually log an exception + +To manually log an exception, you call the `logException` method from the `BugseeManager` class. You can add traces and events to the reported exception. + +```dart +final bugseeManager = Get.I.get(); +bugseeManger.logException(exception: Exception()); +``` + + +### Add attributes + +To attach the user's email to the logged exception use `addEmailAttribute` method and to clear this attribute you can use `clearEmailAttribute`. + +```dart +final bugseeManager = Get.I.get(); +bugseeManger.addEmailAttribute("johndoe@nventive.com"); +//some other code.. +bugseeManger.clearEmailAttribute(); +``` + +for other attributes you can use `addAttributes` with a map where key is the attribute name and value is the attribute value. To clear these attributes use `clearAttribute` and pass the attribute name to it. + +```dart +final bugseeManager = Get.I.get(); +bugseeManger.addAttributes({ + 'name': 'john', + 'age': 45, +}); +//some other code.. +bugseeManger.clearAttribute('name'); +``` + +## 3) Deployment + +The Bugsee token is injected directly in the azure devops pipeline when building the Android/iOS app for release mode in the [Android Build Job](../build/steps-build-android.yml) and [iOS Build Job](../build/steps-build-ios.yml) under the `multiDartDefine` parameter. \ No newline at end of file diff --git a/src/app/.env.dev b/src/app/.env.dev index 40cb1016..5f1cc2c9 100644 --- a/src/app/.env.dev +++ b/src/app/.env.dev @@ -4,6 +4,4 @@ MINIMUM_LEVEL='debug' DAD_JOKES_BASE_URL='https://www.reddit.com/r/dadjokes' APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator -REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=1 -DIAGNOSTIC_ENABLED=true -IS_DATA_OBSCURE=true \ No newline at end of file +REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=1 \ No newline at end of file diff --git a/src/app/.env.prod b/src/app/.env.prod index 9c22677e..1aef1e2b 100644 --- a/src/app/.env.prod +++ b/src/app/.env.prod @@ -4,6 +4,4 @@ MINIMUM_LEVEL='warning' DAD_JOKES_BASE_URL='https://www.reddit.com/r/dadjokes' APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator -REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=720 -DIAGNOSTIC_ENABLED=false -IS_DATA_OBSCURE=true \ No newline at end of file +REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=720 \ No newline at end of file diff --git a/src/app/.env.staging b/src/app/.env.staging index a6d71bd6..88998534 100644 --- a/src/app/.env.staging +++ b/src/app/.env.staging @@ -6,7 +6,7 @@ APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=1 DIAGNOSTIC_ENABLED=true -IS_DATA_OBSCURE=true -DISABLE_LOG_COLLECTION=true -FILTER_LOG_COLLECTION=false -ATTACH_LOG_FILE=true \ No newline at end of file +BUGSEE_IS_DATA_OBSCURE=true +BUGSEE_DISABLE_LOG_COLLECTION=true +BUGSEE_FILTER_LOG_COLLECTION=false +BUGSEE_ATTACH_LOG_FILE=true \ No newline at end of file diff --git a/src/app/lib/access/bugsee/bugsee_configuration_data.dart b/src/app/lib/access/bugsee/bugsee_configuration_data.dart index 5300dbac..8e1d0f15 100644 --- a/src/app/lib/access/bugsee/bugsee_configuration_data.dart +++ b/src/app/lib/access/bugsee/bugsee_configuration_data.dart @@ -4,19 +4,19 @@ final class BugseeConfigurationData extends Equatable { /// Gets whether the Bugsee SDK is enabled or not. if [Null] it fallbacks to a new installed app so it will be enabled. final bool? isBugseeEnabled; - /// Indicate whether the video capturing feature in Bugsee is enabled or not. + /// Indicates whether the video capturing feature in Bugsee is enabled or not. final bool? isVideoCaptureEnabled; - /// Indicate whether bugsee obscure application data in videos and images or not. + /// Indicates whether Bugsee obscures application data in videos and images or not. final bool? isDataObscured; - /// Indicate whether logs are collected or not. + /// Indicates whether logs are collected or not. final bool? isLogCollectionEnabled; - /// Indicate whether logs are filtred during reports or not. + /// Indicates whether logs are filtred during reports or not. final bool? isLogsFilterEnabled; - /// Indicate whether attaching file in the Bugsee report is enabled or not + /// Indicates whether attaching file in the Bugsee report is enabled or not final bool? attachLogFileEnabled; const BugseeConfigurationData({ diff --git a/src/app/lib/access/bugsee/bugsee_repository.dart b/src/app/lib/access/bugsee/bugsee_repository.dart index 0779578f..c78c8dcc 100644 --- a/src/app/lib/access/bugsee/bugsee_repository.dart +++ b/src/app/lib/access/bugsee/bugsee_repository.dart @@ -14,7 +14,7 @@ abstract interface class BugseeRepository { /// Update the current video captured or not flag in shared prefs. Future setIsVideoCaptureEnabled(bool isVideoCaptureEnabled); - /// Update whether data is obscure in shared prefs. + /// Update whether data is obscured in shared prefs. Future setIsDataObscure(bool isDataObscure); /// Update the logCollection flag in shared prefs. @@ -29,23 +29,24 @@ abstract interface class BugseeRepository { final class _BugseeRepository implements BugseeRepository { final String _bugseeEnabledKey = 'bugseeEnabledKey'; - final String _videoCaptureKey = 'videoCaptureKey'; - final String _dataObscureKey = 'dataObscureKey'; - final String _disableLogCollectionKey = 'disableLogCollectionKey'; - final String _disableLogFilterKey = 'disableLogFilterKey'; - final String _attachLogFileKey = 'attachLogFileKey'; + final String _bugseeVideoCaptureKey = 'bugseeVideoCaptureKey'; + final String _bugseeDataObscureKey = 'bugseeDataObscureKey'; + final String _bugseeDisableLogCollectionKey = 'bugseeDisableLogCollectionKey'; + final String _bugseeDisableLogFilterKey = 'bugseeDisableLogFilterKey'; + final String _bugseeAttachLogFileKey = 'bugseeAttachLogFileKey'; @override Future getBugseeConfiguration() async { final sharedPrefInstance = await SharedPreferences.getInstance(); return BugseeConfigurationData( isBugseeEnabled: sharedPrefInstance.getBool(_bugseeEnabledKey), - isVideoCaptureEnabled: sharedPrefInstance.getBool(_videoCaptureKey), - isDataObscured: sharedPrefInstance.getBool(_dataObscureKey), + isVideoCaptureEnabled: sharedPrefInstance.getBool(_bugseeVideoCaptureKey), + isDataObscured: sharedPrefInstance.getBool(_bugseeDataObscureKey), isLogCollectionEnabled: - sharedPrefInstance.getBool(_disableLogCollectionKey), - isLogsFilterEnabled: sharedPrefInstance.getBool(_disableLogFilterKey), - attachLogFileEnabled: sharedPrefInstance.getBool(_attachLogFileKey), + sharedPrefInstance.getBool(_bugseeDisableLogCollectionKey), + isLogsFilterEnabled: + sharedPrefInstance.getBool(_bugseeDisableLogFilterKey), + attachLogFileEnabled: sharedPrefInstance.getBool(_bugseeAttachLogFileKey), ); } @@ -70,13 +71,14 @@ final class _BugseeRepository implements BugseeRepository { final sharedPrefInstance = await SharedPreferences.getInstance(); bool isSaved = await sharedPrefInstance.setBool( - _videoCaptureKey, + _bugseeVideoCaptureKey, isVideoCaptureEnabled, ); if (!isSaved) { throw PersistenceException( - message: 'Error while setting $_videoCaptureKey $isVideoCaptureEnabled', + message: + 'Error while setting $_bugseeVideoCaptureKey $isVideoCaptureEnabled', ); } } @@ -86,13 +88,13 @@ final class _BugseeRepository implements BugseeRepository { final sharedPrefInstance = await SharedPreferences.getInstance(); bool isSaved = await sharedPrefInstance.setBool( - _dataObscureKey, + _bugseeDataObscureKey, isDataObscured, ); if (!isSaved) { throw PersistenceException( - message: 'Error while setting $_dataObscureKey $isDataObscured', + message: 'Error while setting $_bugseeDataObscureKey $isDataObscured', ); } } @@ -102,14 +104,14 @@ final class _BugseeRepository implements BugseeRepository { final sharedPrefInstance = await SharedPreferences.getInstance(); bool isSaved = await sharedPrefInstance.setBool( - _disableLogCollectionKey, + _bugseeDisableLogCollectionKey, isLogCollected, ); if (!isSaved) { throw PersistenceException( message: - 'Error while setting $_disableLogCollectionKey $isLogCollected', + 'Error while setting $_bugseeDisableLogCollectionKey $isLogCollected', ); } } @@ -119,14 +121,14 @@ final class _BugseeRepository implements BugseeRepository { final sharedPrefInstance = await SharedPreferences.getInstance(); bool isSaved = await sharedPrefInstance.setBool( - _disableLogFilterKey, + _bugseeDisableLogFilterKey, isLogFilterEnabled, ); if (!isSaved) { throw PersistenceException( message: - 'Error while setting $_disableLogFilterKey $isLogFilterEnabled', + 'Error while setting $_bugseeDisableLogFilterKey $isLogFilterEnabled', ); } } @@ -136,13 +138,13 @@ final class _BugseeRepository implements BugseeRepository { final sharedPrefInstance = await SharedPreferences.getInstance(); bool isSaved = await sharedPrefInstance.setBool( - _attachLogFileKey, + _bugseeAttachLogFileKey, attachLogFile, ); if (!isSaved) { throw PersistenceException( - message: 'Error while setting $_attachLogFileKey $attachLogFile', + message: 'Error while setting $_bugseeAttachLogFileKey $attachLogFile', ); } } diff --git a/src/app/lib/business/bugsee/bugsee_manager.dart b/src/app/lib/business/bugsee/bugsee_manager.dart index 7748f18f..5f7d8955 100644 --- a/src/app/lib/business/bugsee/bugsee_manager.dart +++ b/src/app/lib/business/bugsee/bugsee_manager.dart @@ -44,8 +44,8 @@ abstract interface class BugseeManager { Future logException({ required Exception exception, StackTrace? stackTrace, - Map? traces, - Map?>? events, + Map traces, + Map?> events, }); /// Manually update the current BugseeEnabled flag in shared prefs and in current manager singleton. @@ -141,16 +141,16 @@ final class _BugseeManager implements BugseeManager { configurationData = configurationData.copyWith( isLogCollectionEnabled: configurationData.isLogCollectionEnabled ?? bool.parse( - dotenv.env['DISABLE_LOG_COLLECTION'] ?? 'true', + dotenv.env['BUGSEE_DISABLE_LOG_COLLECTION'] ?? 'true', ), isLogsFilterEnabled: configurationData.isLogsFilterEnabled ?? bool.parse( - dotenv.env['FILTER_LOG_COLLECTION'] ?? 'true', + dotenv.env['BUGSEE_FILTER_LOG_COLLECTION'] ?? 'true', ), isDataObscured: configurationData.isDataObscured ?? - bool.parse(dotenv.env['IS_DATA_OBSCURE'] ?? 'true'), + bool.parse(dotenv.env['BUGSEE_IS_DATA_OBSCURE'] ?? 'true'), attachLogFileEnabled: configurationData.attachLogFileEnabled ?? - bool.parse(dotenv.env['ATTACH_LOG_FILE'] ?? 'true'), + bool.parse(dotenv.env['BUGSEE_ATTACH_LOG_FILE'] ?? 'true'), ); launchOptions = _initializeLaunchOptions(); @@ -259,22 +259,16 @@ final class _BugseeManager implements BugseeManager { Future logException({ required Exception exception, StackTrace? stackTrace, - Map? traces, - Map?>? events, + Map traces = const {}, + Map?> events = const {}, }) async { if (_currentState.isBugseeEnabled) { - if (traces != null) { - for (var trace in traces.entries) { - await Bugsee.trace(trace.key, trace.value); - } + for (var trace in traces.entries) { + await Bugsee.trace(trace.key, trace.value); } - - if (events != null) { - for (var event in events.entries) { - await Bugsee.event(event.key, event.value); - } + for (var event in events.entries) { + await Bugsee.event(event.key, event.value); } - await Bugsee.logException(exception, stackTrace); } } From 934f7f6b7897118f6758cbea542fba43be92ee92 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Mon, 2 Dec 2024 11:54:27 +0100 Subject: [PATCH 10/14] chore: update Bugsee integration documentation and event caputring method --- doc/Bugsee.md | 211 ++++++++++++------ .../lib/business/bugsee/bugsee_manager.dart | 19 +- src/app/lib/main.dart | 2 +- .../bugsee_configuration_widget.dart | 13 +- 4 files changed, 157 insertions(+), 88 deletions(-) diff --git a/doc/Bugsee.md b/doc/Bugsee.md index b174b027..345de76c 100644 --- a/doc/Bugsee.md +++ b/doc/Bugsee.md @@ -1,53 +1,103 @@ -# Bugsee - -This project include [Bugsee](https://www.bugsee.com/) reporting and Logging, for both Android and iOS apps. -Bugsee lets you monitor and get instant log of unhandled exceptions with traces, events, stacktrace and videos/screenshots of the reported exception. More features are provided by Bugsee like data obscuration and log filter. - -For this implementation we've used [bugsee_flutter](https://pub.dev/packages/bugsee_flutter) package. - -- By default only release apps will have Bugsee reporting enabled, to enable Bugsee in debug mode add your token in `launch.json` add remove the check on debug mode in `BugseeManager` class. -- **Token** - - Generate your token in order to test Bugsee logging and reporting - - Head to [Bugsee dashboard](https://www.bugsee.com/) - - Create a new account - - Create a new Android/iOS (choose Flutter framework) - - Copy-paste the generated token into `launch.json` - - -## 1) Features - -In this project we've implemented the following features of Bugsee: -- [Manual invocation](https://docs.bugsee.com/sdk/flutter/manual/) helpfull for developers to test their Bugsee integration and setup with new tokens. -- [Custom data](https://docs.bugsee.com/sdk/flutter/custom/) custom data could be attached to the logged exceptions (emails or other type of data) - - [Email](https://docs.bugsee.com/sdk/flutter/custom/#:~:text=Adding%20custom%20data-,User%20email,-When%20you%20already) this will identify the user whom experienced the exception/bug this will update the exception dashboard item from anonymous to the user's mail this data will be reported with every logged exception unless the app is deleted or removed manually. - - [Attributes](https://docs.bugsee.com/sdk/flutter/custom/#:~:text=User/Session%20attributes) attributes related to the user info, will be reported with every logged exception unless the app is deleted or removed manually. - - [Traces](https://docs.bugsee.com/sdk/flutter/custom/#:~:text=them%0ABugsee.clearAllAttributes()%3B-,Custom%20traces,-Traces%20may%20be) helpfull when developer want to track the change of specific value before the logging of the exception. - - [Events](https://docs.bugsee.com/sdk/flutter/custom/#:~:text=Custom%20events-,Events,-are%20identified%20by) highlight on which event the exception is logged, accept also json data attached to it. -- [Exception Logging](https://docs.bugsee.com/sdk/flutter/logs/) the app automatically log every unhandled exception: Dart SDK exception are related to data or logic errors and Flutter SDK errors that are related to layout and rendering issues. The implementation also offer an API to manually log an exception with traces and events. -- [Video Capturing](https://docs.bugsee.com/sdk/flutter/privacy/video/) video capturing is by default enabled in this project, but it can be turned off using the `videoEnabled` flag in the launchOptions object for Android and iOS. -- [Log reporting](https://docs.bugsee.com/sdk/flutter/privacy/logs/) all logs are filtered by default using the `_filterBugseeLogs` method, this can be turned off from the app or by removing the call to `setLogFilter` Bugsee method. -- [Obscure Data](https://docs.bugsee.com/sdk/flutter/privacy/video/#:~:text=Protecting%20flutter%20views): data obscuration is by default enabled in the project in order to protect user-related data from being leaked through captured videos. - -**Default configurations:** -Data obscuration, log collection, log filter and attaching log file features are initialized from the `.env.staging` file. -``` -BUGSEE_IS_DATA_OBSCURE=true -BUGSEE_DISABLE_LOG_COLLECTION=true -BUGSEE_FILTER_LOG_COLLECTION=false -BUGSEE_ATTACH_LOG_FILE=true +# Bugsee Integration Documentation + +This document provides a comprehensive guide to integrating and using **Bugsee** in your mobile application. Bugsee is a powerful tool for monitoring and debugging your app by capturing and reporting unhandled exceptions, providing insights into app crashes, user interactions, and more. + +## **Overview** + +**Bugsee** helps developers quickly identify and troubleshoot crashes, bugs, and performance issues in mobile applications. By integrating Bugsee, developers can capture detailed logs, screen recordings, and contextual data (such as user attributes) to understand and fix issues faster. + +--- + +## **Features** + +This implementation of Bugsee leverages the following features to provide robust exception tracking and reporting: + +### 1. **Manual Invocation** + - Developers can trigger Bugsee for testing purposes or to verify the integration. You can also use different tokens for testing in different environments. + - Documentation: [Bugsee SDK Docs](https://docs.bugsee.com/) + +### 2. **Custom Data Reporting** + - Add additional user-specific data (like email addresses) or custom attributes to exception reports for better context. + - **Email:** Helps identify the specific user experiencing issues. + - **Attributes:** Attach custom key-value pairs for further context. + - **Traces:** Track specific values or conditions before an exception occurs. + - **Events:** Log events leading to exceptions and attach structured JSON data for detailed insights. + - Documentation: [Bugsee Custom Data](https://docs.bugsee.com/) + +### 3. **Exception Logging** + - Bugsee automatically captures unhandled exceptions in your Dart and Flutter code. + - **Dart Exceptions:** Captures logic and data errors. + - **Flutter Exceptions:** Captures rendering and layout errors. + - You can also manually log exceptions with additional context, such as traces and events. + - Documentation: [Bugsee Exception Logging](https://bugsee.com/) + +### 4. **Video Capture** + - Bugsee automatically captures screen recordings of user interactions that lead to exceptions. This helps developers visually understand what the user was doing when the issue occurred. + - You can disable video capture by setting the `videoEnabled` flag. + - Documentation: [Bugsee Flutter Installation](https://docs.bugsee.com/sdk/flutter/installation/) + +### 5. **Log Reporting and Filtering** + - Bugsee integrates with your app’s logging system. By default, logs are filtered to remove sensitive information to protect user privacy. + - You can customize log collection behavior using configuration options. + - Documentation: [Bugsee Log Reporting](https://docs.bugsee.com/sdk/flutter/installation/) + +### 6. **Data Obfuscation** + - Sensitive user data (like passwords or personal information) in captured videos is automatically obscured by default to prevent leaks. + - Documentation: [Bugsee Data Obscuration](https://docs.bugsee.com/sdk/flutter/installation/) + +--- + +## **Default Configurations** + +Bugsee’s behavior can be controlled via environment settings, particularly for data obscuration, log collection, and file attachment. The default configurations are defined in the `.env.staging` file as follows: + +```env +BUGSEE_IS_DATA_OBSCURE=true # Enables data obscuration for captured videos +BUGSEE_DISABLE_LOG_COLLECTION=true # Disables log collection by default +BUGSEE_FILTER_LOG_COLLECTION=false # Allows all logs unless manually filtered +BUGSEE_ATTACH_LOG_FILE=true # Attaches log files with Bugsee reports ``` -## 2) Implementation -- [Bugsee Manager](../src/app/lib/business/bugsee/bugsee_manager.dart): a service class that handle the Bugsee intialization, capturing logs, customize Bugsee fields and features (Video capture, data obscure, logs filter...) . -- [Bugsee Config State](../src/app/lib/business/bugsee/bugsee_config_state.dart): a state class holds all the actual Bugsee features status (whether enabled or not). -- [Bugsee Repository](../src/app/lib/access/bugsee/bugsee_repository.dart): save and retrieve Bugsee configuration from the shared preference storage. -- [Bugsee saved configuration](../src/app/lib/access/bugsee/bugsee_configuration_data.dart): holds the Bugsee saved configuration in shared preference, used in [Bugsee Manager](../src/app/lib/business/bugsee/bugsee_manager.dart) to initialize [Bugsee Config State](../src/app/lib/business/bugsee/bugsee_config_state.dart). +Ensure that these values are properly set for different environments (e.g., staging, production). + +--- + +## **Implementation Details** + +The Bugsee integration consists of several key components for handling configuration, exception tracking, and reporting. + +### 1. **[Bugsee Manager](../src/app/lib/business/bugsee/bugsee_manager.dart)** + - Responsible for initializing Bugsee, capturing logs, and configuring Bugsee features (like video capture, data obfuscation, and log filtering). + +### 2. **[Bugsee Config State](../src/app/lib/business/bugsee/bugsee_config_state.dart)** + - Maintains the current state of Bugsee’s features (enabled/disabled) within the app. + +### 3. **[Bugsee Repository](../src/app/lib/access/bugsee/bugsee_repository.dart)** + - Handles the saving and retrieving of Bugsee configurations from shared preferences. + +### 4. **[Bugsee Saved Configuration](../src/app/lib/access/bugsee/bugsee_configuration_data.dart)** + - Stores and manages the saved configurations used to initialize Bugsee upon app launch. -### Intecepting exceptions -Bugsee implementation in this project, by default intecepts all the unhandled Dart and Flutter exception. +--- + +## **Exception Handling and Reporting** + +### Intercepting Exceptions +By default, Bugsee intercepts all unhandled Dart and Flutter exceptions globally: + +1. **Dart Exceptions**: + - These are data or logic errors that happen within your Dart code. + +2. **Flutter Exceptions**: + - These occur during layout or rendering issues. + +Both types of exceptions are captured and reported to Bugsee’s dashboard. + +The exceptions are intercepted using `runZonedGuarded` and `FlutterError.onError`: -`main.dart` ```dart +// main.dart + runZonedGuarded( () async { FlutterError.onError = @@ -58,11 +108,9 @@ runZonedGuarded( }, GetIt.I.get().inteceptExceptions, ); -``` -the `inteceptExceptions` and `inteceptRenderExceptions` recerespectively report Dart and Flutter exceptions to the Bugsee Dashboard. -`inteceptExceptions` -```dart +// bugsee_manager.dart + @override Future inteceptExceptions( Object error, @@ -80,11 +128,8 @@ the `inteceptExceptions` and `inteceptRenderExceptions` recerespectively report }, ); } -``` -`inteceptRenderExceptions` -```dart -@override + @override Future inteceptRenderExceptions(FlutterErrorDetails error) async { await logException( exception: Exception(error.exception), @@ -93,49 +138,69 @@ the `inteceptExceptions` and `inteceptRenderExceptions` recerespectively report } ``` -### Manually invoke report dialog +### **Manually Reporting Issues** -To manually display the report dialog, you call the `showCaptureLogReport` method from the `BugseeManager` class. +You can manually trigger Bugsee to capture logs and display a report dialog using the `showCaptureLogReport` method: ```dart final bugseeManager = Get.I.get(); -bugseeManger.showCaptureLogReport(); +bugseeManager.showCaptureLogReport(); ``` +This is useful for debugging specific scenarios or reporting custom issues. -### Manually log an exception +### **Manually Logging Exceptions** -To manually log an exception, you call the `logException` method from the `BugseeManager` class. You can add traces and events to the reported exception. +To manually log an exception (with or without additional traces), use the `logException` method: ```dart final bugseeManager = Get.I.get(); -bugseeManger.logException(exception: Exception()); +bugseeManager.logException(exception: Exception("Custom error")); ``` +You can add additional context **traces**: -### Add attributes +```dart +bugseeManager.logException( + exception: Exception("Custom error"), + traces: ["Trace 1", "Trace 2"], // Add relevant traces +); +``` + +### **Adding User Attributes** -To attach the user's email to the logged exception use `addEmailAttribute` method and to clear this attribute you can use `clearEmailAttribute`. +To provide more context about the user experiencing the issue, you can add custom attributes such as an email address: + +- **Add Email Attribute**: ```dart final bugseeManager = Get.I.get(); -bugseeManger.addEmailAttribute("johndoe@nventive.com"); -//some other code.. -bugseeManger.clearEmailAttribute(); +bugseeManager.addEmailAttribute("johndoe@nventive.com"); ``` -for other attributes you can use `addAttributes` with a map where key is the attribute name and value is the attribute value. To clear these attributes use `clearAttribute` and pass the attribute name to it. +- **Clear Email Attribute**: ```dart -final bugseeManager = Get.I.get(); -bugseeManger.addAttributes({ - 'name': 'john', - 'age': 45, +bugseeManager.clearEmailAttribute(); +``` + +- **Add Custom Attributes**: + +You can also add custom key-value pairs as additional attributes to enrich the exception reports: + +```dart +bugseeManager.addAttributes({ + "userType": "premium", + "accountStatus": "active" }); -//some other code.. -bugseeManger.clearAttribute('name'); ``` -## 3) Deployment +## **Additional Resources** + +- [Bugsee SDK Documentation](https://docs.bugsee.com/) +- [Bugsee Flutter Installation Guide](https://docs.bugsee.com/sdk/flutter/installation/) +- [bugsee_flutter package](https://pub.dev/packages/bugsee_flutter) +- [Handling Flutter errors](https://docs.flutter.dev/testing/errors) +- [runZoneGuarded Error handling](https://api.flutter.dev/flutter/dart-async/runZonedGuarded.html) -The Bugsee token is injected directly in the azure devops pipeline when building the Android/iOS app for release mode in the [Android Build Job](../build/steps-build-android.yml) and [iOS Build Job](../build/steps-build-ios.yml) under the `multiDartDefine` parameter. \ No newline at end of file +--- diff --git a/src/app/lib/business/bugsee/bugsee_manager.dart b/src/app/lib/business/bugsee/bugsee_manager.dart index 5f7d8955..592fc5e4 100644 --- a/src/app/lib/business/bugsee/bugsee_manager.dart +++ b/src/app/lib/business/bugsee/bugsee_manager.dart @@ -45,7 +45,6 @@ abstract interface class BugseeManager { required Exception exception, StackTrace? stackTrace, Map traces, - Map?> events, }); /// Manually update the current BugseeEnabled flag in shared prefs and in current manager singleton. @@ -76,7 +75,7 @@ abstract interface class BugseeManager { Future inteceptExceptions(Object error, StackTrace stackTrace); /// Intercept all unhandled rending exception thrown by the Flutter framework - Future inteceptRenderExceptions(FlutterErrorDetails error); + Future inteceptRenderingExceptions(FlutterErrorDetails error); /// Manually add a map of attributes /// - the map entry key is the attribute name @@ -97,6 +96,9 @@ abstract interface class BugseeManager { /// Manually remove an attribute by the given key attached using [addAttributes] Future clearAttribute(String attribute); + + /// Manually log Bugsee events that will be attached to the reported issues + void logEvents(Map> events); } final class _BugseeManager implements BugseeManager { @@ -260,15 +262,11 @@ final class _BugseeManager implements BugseeManager { required Exception exception, StackTrace? stackTrace, Map traces = const {}, - Map?> events = const {}, }) async { if (_currentState.isBugseeEnabled) { for (var trace in traces.entries) { await Bugsee.trace(trace.key, trace.value); } - for (var event in events.entries) { - await Bugsee.event(event.key, event.value); - } await Bugsee.logException(exception, stackTrace); } } @@ -404,10 +402,17 @@ final class _BugseeManager implements BugseeManager { } @override - Future inteceptRenderExceptions(FlutterErrorDetails error) async { + Future inteceptRenderingExceptions(FlutterErrorDetails error) async { await logException( exception: Exception(error.exception), stackTrace: error.stack, ); } + + @override + void logEvents(Map> events) async { + for (var event in events.entries) { + Bugsee.event(event.key, event.value); + } + } } diff --git a/src/app/lib/main.dart b/src/app/lib/main.dart index 770a6cb9..8b55bcfe 100644 --- a/src/app/lib/main.dart +++ b/src/app/lib/main.dart @@ -41,7 +41,7 @@ Future main() async { runZonedGuarded( () async { FlutterError.onError = - GetIt.I.get().inteceptRenderExceptions; + GetIt.I.get().inteceptRenderingExceptions; await initializeComponents(); await registerBugseeManager(); runApp(const App()); diff --git a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart index 7d048bb1..8c95f58a 100644 --- a/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart +++ b/src/app/lib/presentation/diagnostic/bugsee_configuration_widget.dart @@ -89,7 +89,7 @@ class _BugseeConfigurationWidgetState extends State { }, ), DiagnosticSwitch( - label: 'Enabling log collection', + label: 'Log collection enabled', value: state.isLogCollectionEnabled, onChanged: (value) async { await bugseeManager.setIsLogsCollectionEnabled(value); @@ -99,7 +99,7 @@ class _BugseeConfigurationWidgetState extends State { }, ), DiagnosticSwitch( - label: 'Enable log filter', + label: 'Filter log enabled', value: state.isLogFilterEnabled, onChanged: (value) async { await bugseeManager.setIsLogFilterEnabeld(value); @@ -109,7 +109,7 @@ class _BugseeConfigurationWidgetState extends State { }, ), DiagnosticSwitch( - label: 'Attach log file', + label: 'Log file attached', value: state.attachLogFile, onChanged: (value) async { bugseeManager.setAttachLogFileEnabled(value); @@ -127,11 +127,10 @@ class _BugseeConfigurationWidgetState extends State { }, ), DiagnosticButton( - label: 'Log Exception with events', + label: 'Add events to the exception', onPressed: () { - bugseeManager.logException( - exception: Exception(), - events: { + bugseeManager.logEvents( + { 'data': { 'date': DateTime.now().millisecondsSinceEpoch, 'id': Random().nextInt(20), From 035775ead71da7d7ffd78c734846dca226e9ccfb Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Tue, 25 Feb 2025 16:10:08 +0100 Subject: [PATCH 11/14] chore: create global exception interceptor --- doc/Bugsee.md | 48 +++++++++---------- src/app/.env.dev | 3 +- src/app/.env.prod | 3 +- src/app/integration_test/bugsee_test.dart | 9 ---- .../integration_test/integration_test.dart | 8 ---- .../business/bugsee/bugsee_config_state.dart | 37 +++++++------- .../lib/business/bugsee/bugsee_manager.dart | 29 ++++------- .../inteceptor/exception_inteceptor.dart | 43 +++++++++++++++++ src/app/lib/main.dart | 26 ++++++---- src/app/pubspec.lock | 28 +++++------ src/cli/CHANGELOG.md | 18 +++---- 11 files changed, 139 insertions(+), 113 deletions(-) delete mode 100644 src/app/integration_test/bugsee_test.dart create mode 100644 src/app/lib/business/inteceptor/exception_inteceptor.dart diff --git a/doc/Bugsee.md b/doc/Bugsee.md index 345de76c..331f672d 100644 --- a/doc/Bugsee.md +++ b/doc/Bugsee.md @@ -2,21 +2,21 @@ This document provides a comprehensive guide to integrating and using **Bugsee** in your mobile application. Bugsee is a powerful tool for monitoring and debugging your app by capturing and reporting unhandled exceptions, providing insights into app crashes, user interactions, and more. -## **Overview** +## Overview **Bugsee** helps developers quickly identify and troubleshoot crashes, bugs, and performance issues in mobile applications. By integrating Bugsee, developers can capture detailed logs, screen recordings, and contextual data (such as user attributes) to understand and fix issues faster. --- -## **Features** +## Features This implementation of Bugsee leverages the following features to provide robust exception tracking and reporting: -### 1. **Manual Invocation** +### 1. Manual Invocation - Developers can trigger Bugsee for testing purposes or to verify the integration. You can also use different tokens for testing in different environments. - Documentation: [Bugsee SDK Docs](https://docs.bugsee.com/) -### 2. **Custom Data Reporting** +### 2. Custom Data Reporting - Add additional user-specific data (like email addresses) or custom attributes to exception reports for better context. - **Email:** Helps identify the specific user experiencing issues. - **Attributes:** Attach custom key-value pairs for further context. @@ -24,30 +24,30 @@ This implementation of Bugsee leverages the following features to provide robust - **Events:** Log events leading to exceptions and attach structured JSON data for detailed insights. - Documentation: [Bugsee Custom Data](https://docs.bugsee.com/) -### 3. **Exception Logging** +### 3. Exception Logging - Bugsee automatically captures unhandled exceptions in your Dart and Flutter code. - **Dart Exceptions:** Captures logic and data errors. - **Flutter Exceptions:** Captures rendering and layout errors. - You can also manually log exceptions with additional context, such as traces and events. - Documentation: [Bugsee Exception Logging](https://bugsee.com/) -### 4. **Video Capture** +### 4. Video Capture - Bugsee automatically captures screen recordings of user interactions that lead to exceptions. This helps developers visually understand what the user was doing when the issue occurred. - You can disable video capture by setting the `videoEnabled` flag. - Documentation: [Bugsee Flutter Installation](https://docs.bugsee.com/sdk/flutter/installation/) -### 5. **Log Reporting and Filtering** +### 5. Log Reporting and Filtering - Bugsee integrates with your app’s logging system. By default, logs are filtered to remove sensitive information to protect user privacy. - You can customize log collection behavior using configuration options. - Documentation: [Bugsee Log Reporting](https://docs.bugsee.com/sdk/flutter/installation/) -### 6. **Data Obfuscation** +### 6. Data Obfuscation - Sensitive user data (like passwords or personal information) in captured videos is automatically obscured by default to prevent leaks. - Documentation: [Bugsee Data Obscuration](https://docs.bugsee.com/sdk/flutter/installation/) --- -## **Default Configurations** +## Default Configurations Bugsee’s behavior can be controlled via environment settings, particularly for data obscuration, log collection, and file attachment. The default configurations are defined in the `.env.staging` file as follows: @@ -62,33 +62,33 @@ Ensure that these values are properly set for different environments (e.g., stag --- -## **Implementation Details** +## Implementation Details The Bugsee integration consists of several key components for handling configuration, exception tracking, and reporting. -### 1. **[Bugsee Manager](../src/app/lib/business/bugsee/bugsee_manager.dart)** +### 1. [Bugsee Manager](../src/app/lib/business/bugsee/bugsee_manager.dart) - Responsible for initializing Bugsee, capturing logs, and configuring Bugsee features (like video capture, data obfuscation, and log filtering). -### 2. **[Bugsee Config State](../src/app/lib/business/bugsee/bugsee_config_state.dart)** +### 2. [Bugsee Config State](../src/app/lib/business/bugsee/bugsee_config_state.dart) - Maintains the current state of Bugsee’s features (enabled/disabled) within the app. -### 3. **[Bugsee Repository](../src/app/lib/access/bugsee/bugsee_repository.dart)** +### 3. [Bugsee Repository](../src/app/lib/access/bugsee/bugsee_repository.dart) - Handles the saving and retrieving of Bugsee configurations from shared preferences. -### 4. **[Bugsee Saved Configuration](../src/app/lib/access/bugsee/bugsee_configuration_data.dart)** +### 4. [Bugsee Saved Configuration](../src/app/lib/access/bugsee/bugsee_configuration_data.dart) - Stores and manages the saved configurations used to initialize Bugsee upon app launch. --- -## **Exception Handling and Reporting** +## Exception Handling and Reporting ### Intercepting Exceptions By default, Bugsee intercepts all unhandled Dart and Flutter exceptions globally: -1. **Dart Exceptions**: +1. Dart Exceptions: - These are data or logic errors that happen within your Dart code. -2. **Flutter Exceptions**: +2. Flutter Exceptions: - These occur during layout or rendering issues. Both types of exceptions are captured and reported to Bugsee’s dashboard. @@ -138,7 +138,7 @@ runZonedGuarded( } ``` -### **Manually Reporting Issues** +### Manually Reporting Issues You can manually trigger Bugsee to capture logs and display a report dialog using the `showCaptureLogReport` method: @@ -149,7 +149,7 @@ bugseeManager.showCaptureLogReport(); This is useful for debugging specific scenarios or reporting custom issues. -### **Manually Logging Exceptions** +### Manually Logging Exceptions To manually log an exception (with or without additional traces), use the `logException` method: @@ -167,24 +167,24 @@ bugseeManager.logException( ); ``` -### **Adding User Attributes** +### Adding User Attributes To provide more context about the user experiencing the issue, you can add custom attributes such as an email address: -- **Add Email Attribute**: +- Add Email Attribute: ```dart final bugseeManager = Get.I.get(); bugseeManager.addEmailAttribute("johndoe@nventive.com"); ``` -- **Clear Email Attribute**: +- Clear Email Attribute: ```dart bugseeManager.clearEmailAttribute(); ``` -- **Add Custom Attributes**: +- Add Custom Attributes: You can also add custom key-value pairs as additional attributes to enrich the exception reports: @@ -195,7 +195,7 @@ bugseeManager.addAttributes({ }); ``` -## **Additional Resources** +## Additional Resources - [Bugsee SDK Documentation](https://docs.bugsee.com/) - [Bugsee Flutter Installation Guide](https://docs.bugsee.com/sdk/flutter/installation/) diff --git a/src/app/.env.dev b/src/app/.env.dev index 5f1cc2c9..22d74e66 100644 --- a/src/app/.env.dev +++ b/src/app/.env.dev @@ -4,4 +4,5 @@ MINIMUM_LEVEL='debug' DAD_JOKES_BASE_URL='https://www.reddit.com/r/dadjokes' APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator -REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=1 \ No newline at end of file +REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=1 +DIAGNOSTIC_ENABLED=true \ No newline at end of file diff --git a/src/app/.env.prod b/src/app/.env.prod index 1aef1e2b..88d164e4 100644 --- a/src/app/.env.prod +++ b/src/app/.env.prod @@ -4,4 +4,5 @@ MINIMUM_LEVEL='warning' DAD_JOKES_BASE_URL='https://www.reddit.com/r/dadjokes' APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator -REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=720 \ No newline at end of file +REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=720 +DIAGNOSTIC_ENABLED=true \ No newline at end of file diff --git a/src/app/integration_test/bugsee_test.dart b/src/app/integration_test/bugsee_test.dart deleted file mode 100644 index bb2c4c2f..00000000 --- a/src/app/integration_test/bugsee_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -/// Test all Bugsee setup features -Future bugseeSetupTest() async { - testWidgets( - 'Test Bugsee configuration', - (tester) async {}, - ); -} diff --git a/src/app/integration_test/integration_test.dart b/src/app/integration_test/integration_test.dart index 18187bc0..e4d1f620 100644 --- a/src/app/integration_test/integration_test.dart +++ b/src/app/integration_test/integration_test.dart @@ -4,7 +4,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:integration_test/integration_test.dart'; -import 'bugsee_test.dart'; import 'dad_jokes_page_test.dart'; import 'forced_update_test.dart'; import 'kill_switch_test.dart'; @@ -13,12 +12,6 @@ import 'kill_switch_test.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); await initializeComponents(isMocked: true); - await registerBugseeManager( - isMock: true, - //A mock hexadecimal-based Bugsee token - bugseeToken: '01234567-0123-0123-0123-0123456789AB', - ); - tearDownAll( () async => await GetIt.I.get().setMocking(false), ); @@ -26,5 +19,4 @@ Future main() async { await dadJokeTest(); await killSwitchTest(); await forcedUpdateTest(); - await bugseeSetupTest(); } diff --git a/src/app/lib/business/bugsee/bugsee_config_state.dart b/src/app/lib/business/bugsee/bugsee_config_state.dart index 8276b309..4d1006b5 100644 --- a/src/app/lib/business/bugsee/bugsee_config_state.dart +++ b/src/app/lib/business/bugsee/bugsee_config_state.dart @@ -12,51 +12,52 @@ enum ConfigErrorEnum { } final class BugseeConfigState extends Equatable { - /// Indicate if the app require a restart to reactivate the bugsee configurations + /// Indicates if the app requires a restart to reactivate the Bugsee configurations. /// - /// `true` only if `isConfigurationValid == true` and bugsee is turned on + /// `true` only if `isConfigurationValid == true` and Bugsee is turned on final bool isRestartRequired; - /// Indicate if bugsee is enabled or not - /// by default bugsee is enabled if `isConfigurationValid == true`. + /// Indicates if Bugsee is enabled or not. + /// by default Bugsee is enabled if `isConfigurationValid == true`. final bool isBugseeEnabled; - /// Indicate whether video capturing is enabled or not. + /// Indicates whether video capturing is enabled or not. /// enabled by default if `isBugseeEnabled == true`. /// - /// cannot be true if `isBugseeEnabled == false`. + /// Cannot be true if `isBugseeEnabled == false`. final bool isVideoCaptureEnabled; - /// Indicate if bugsee configuration is valid - /// config is valid if app in release mode and the provided token is valid + /// Indicates if Bugsee configuration is valid. + /// + /// Configuration is valid if app in release mode and the provided token is valid /// following the [bugseeTokenFormat] regex. final bool isConfigurationValid; - /// Indicate whether data is obscured in report videos + /// Indicates whether data is obscured in report videos. /// - /// cannot be true if `isBugseeEnabled == false`. + /// Cannot be true if `isBugseeEnabled == false`. final bool isDataObscured; - /// Indicate whether log will be collected during Bugsee reporting or not + /// Indicates whether log will be collected during Bugsee reporting or not, /// by default logs are collected but filterd. /// /// This value is initialized from [dotenv.env] and shared prefs storage. final bool isLogCollectionEnabled; - /// Indicate whether log will be filterd or not - /// by default all logs are filted using [bugseeFilterRegex] defined in [BugseeManager] + /// Indicates whether log will be filterd or not, by default all logs are + /// filted using [bugseeFilterRegex] defined in [BugseeManager]. /// /// This value is initialized from [dotenv.env] map and shared prefs storage. final bool isLogFilterEnabled; - /// Indicate whether Bugsee will attach the log file when reporting crashes/exceptions - /// or not + /// Indicates whether Bugsee will attach the log file when + /// reporting crashes/exception or not. /// - /// The initial value is taken from [dotenv.env] and shared prefs. - /// By default it's enabled. + /// The initial value is taken from [dotenv.env] and shared preferences. final bool attachLogFile; - /// Indicate the configuration error type (debug, invalid token or invalid platform) + /// Indicates the configuration error type and message + /// (debug, invalid token or invalid platform). final ConfigErrorEnum? configErrorEnum; const BugseeConfigState({ diff --git a/src/app/lib/business/bugsee/bugsee_manager.dart b/src/app/lib/business/bugsee/bugsee_manager.dart index 592fc5e4..b8c2c3a9 100644 --- a/src/app/lib/business/bugsee/bugsee_manager.dart +++ b/src/app/lib/business/bugsee/bugsee_manager.dart @@ -17,7 +17,11 @@ const String bugseeFilterRegex = r'.'; /// Service related to initializing Bugsee service abstract interface class BugseeManager { - factory BugseeManager() = _BugseeManager; + factory BugseeManager({ + required BugseeRepository bugseeRepository, + required Logger logger, + required LoggerManager loggerManager, + }) = _BugseeManager; /// Current BugseeManager state BugseeConfigState get bugseeConfigState; @@ -28,10 +32,6 @@ abstract interface class BugseeManager { /// [BUGSEE_TOKEN] in the env using `--dart-define` or `launch.json` on vscode Future initialize({ String? bugseeToken, - bool isMock, - required Logger logger, - required LoggerManager loggerManager, - required BugseeRepository bugseeRepository, }); /// Manually log a provided exception with a stack trace @@ -106,7 +106,11 @@ final class _BugseeManager implements BugseeManager { late LoggerManager loggerManager; late BugseeRepository bugseeRepository; - _BugseeManager(); + _BugseeManager({ + required this.bugseeRepository, + required this.logger, + required this.loggerManager, + }); BugseeConfigState _currentState = const BugseeConfigState(); @@ -121,15 +125,7 @@ final class _BugseeManager implements BugseeManager { @override Future initialize({ String? bugseeToken, - bool isMock = false, - required Logger logger, - required LoggerManager loggerManager, - required BugseeRepository bugseeRepository, }) async { - this.logger = logger; - this.loggerManager = loggerManager; - this.bugseeRepository = bugseeRepository; - if (!Platform.isIOS && !Platform.isAndroid) { _currentState = _currentState.copyWith( isConfigurationValid: false, @@ -158,11 +154,6 @@ final class _BugseeManager implements BugseeManager { launchOptions = _initializeLaunchOptions(); _isBugSeeInitialized = false; - if (isMock) { - _initializeBugsee(bugseeToken ?? ''); - return; - } - if (kDebugMode) { _currentState = _currentState.copyWith( isConfigurationValid: false, diff --git a/src/app/lib/business/inteceptor/exception_inteceptor.dart b/src/app/lib/business/inteceptor/exception_inteceptor.dart new file mode 100644 index 00000000..37030bcc --- /dev/null +++ b/src/app/lib/business/inteceptor/exception_inteceptor.dart @@ -0,0 +1,43 @@ +import 'package:app/business/bugsee/bugsee_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:logger/logger.dart'; + +abstract interface class ExceptionInteceptor { + factory ExceptionInteceptor({ + required BugseeManager bugseeManager, + required Logger logger, + }) = _ExceptionInteceptor; + + /// Intecepts exceptions, logs them and sends them to Bugsee. + void inteceptException(Object exception, StackTrace stackTrace); + + /// Intecepts rendering exceptions, logs them and sends them to Bugsee. + Future inteceptRenderingExceptions(FlutterErrorDetails error); +} + +final class _ExceptionInteceptor implements ExceptionInteceptor { + final BugseeManager bugseeManager; + final Logger logger; + + _ExceptionInteceptor({ + required this.bugseeManager, + required this.logger, + }); + + @override + void inteceptException(Object exception, StackTrace stackTrace) { + logger.e( + exception, + stackTrace: stackTrace, + ); + bugseeManager.inteceptExceptions(exception, stackTrace); + } + + @override + Future inteceptRenderingExceptions(FlutterErrorDetails error) async { + logger.e( + error, + ); + bugseeManager.inteceptRenderingExceptions(error); + } +} diff --git a/src/app/lib/main.dart b/src/app/lib/main.dart index 8b55bcfe..04f952bc 100644 --- a/src/app/lib/main.dart +++ b/src/app/lib/main.dart @@ -25,6 +25,7 @@ import 'package:app/business/diagnostics/diagnostics_service.dart'; import 'package:app/business/environment/environment.dart'; import 'package:app/business/environment/environment_manager.dart'; import 'package:app/business/forced_update/update_required_service.dart'; +import 'package:app/business/inteceptor/exception_inteceptor.dart'; import 'package:app/business/kill_switch/kill_switch_service.dart'; import 'package:app/business/logger/logger_manager.dart'; import 'package:app/business/mocking/mocking_manager.dart'; @@ -41,12 +42,12 @@ Future main() async { runZonedGuarded( () async { FlutterError.onError = - GetIt.I.get().inteceptRenderingExceptions; + GetIt.I.get().inteceptRenderingExceptions; await initializeComponents(); await registerBugseeManager(); runApp(const App()); }, - GetIt.I.get().inteceptExceptions, + GetIt.I.get().inteceptException, ); } @@ -131,21 +132,26 @@ Future _registerAndLoadLoggers() async { void _initializeBugseeManager() { GetIt.I.registerSingleton(BugseeRepository()); GetIt.I.registerSingleton( - BugseeManager(), + BugseeManager( + bugseeRepository: GetIt.I.get(), + logger: GetIt.I.get(), + loggerManager: GetIt.I.get(), + ), + ); + GetIt.I.registerSingleton( + ExceptionInteceptor( + bugseeManager: GetIt.I.get(), + logger: _logger, + ), ); } -Future registerBugseeManager({bool? isMock, String? bugseeToken}) async { +Future registerBugseeManager() async { if (!GetIt.I.isRegistered()) { _initializeBugseeManager(); } GetIt.I.get().initialize( - bugseeToken: - bugseeToken ?? const String.fromEnvironment('BUGSEE_TOKEN'), - logger: GetIt.I.get(), - loggerManager: GetIt.I.get(), - bugseeRepository: GetIt.I.get(), - isMock: isMock ?? false, + bugseeToken: const String.fromEnvironment('BUGSEE_TOKEN'), ); } diff --git a/src/app/pubspec.lock b/src/app/pubspec.lock index 188b15b7..bb903a89 100644 --- a/src/app/pubspec.lock +++ b/src/app/pubspec.lock @@ -544,18 +544,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -600,18 +600,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -784,10 +784,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -1085,10 +1085,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" timezone: dependency: transitive description: @@ -1205,10 +1205,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/src/cli/CHANGELOG.md b/src/cli/CHANGELOG.md index 01b8b9c3..703bfe98 100644 --- a/src/cli/CHANGELOG.md +++ b/src/cli/CHANGELOG.md @@ -6,15 +6,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) Prefix your items with `(Template)` if the change is about the template and not the resulting application. ## 0.22.0 -- Add bugsee global inteceptor for dart exceptions to template app -- Add bugsee global inteceptor for flutter layout exceptions to template app -- Add bugsee custom attributes logging -- Add bugsee custom events logging -- Add bugsee custom traces logging -- Implement log file attach to reported bugsee exceptions -- Implement obsucre data feature from reported videos -- Implement Bugsee enable log and log filter features -- Update diagnostic overlay to test Bugsee advanced features +- Add bugsee global inteceptor for dart exceptions to template app. +- Add bugsee global inteceptor for flutter layout exceptions to template app. +- Add bugsee custom attributes logging. +- Add bugsee custom events logging. +- Add bugsee custom traces logging. +- Implement log file attach to reported bugsee exceptions. +- Implement obsucre data feature from reported videos. +- Implement Bugsee enable log and log filter features. +- Update diagnostic overlay to test Bugsee advanced features. ## 0.21.0 From 5ac9d49868406e4ef6f5fba401980da5785e5ea9 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Thu, 27 Feb 2025 16:59:23 +0100 Subject: [PATCH 12/14] chore: update code documentation in exception interceptor --- src/app/lib/business/inteceptor/exception_inteceptor.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/lib/business/inteceptor/exception_inteceptor.dart b/src/app/lib/business/inteceptor/exception_inteceptor.dart index 37030bcc..cc4ef4bb 100644 --- a/src/app/lib/business/inteceptor/exception_inteceptor.dart +++ b/src/app/lib/business/inteceptor/exception_inteceptor.dart @@ -8,10 +8,10 @@ abstract interface class ExceptionInteceptor { required Logger logger, }) = _ExceptionInteceptor; - /// Intecepts exceptions, logs them and sends them to Bugsee. + /// Intercepts exceptions, logs them and sends them to Bugsee. void inteceptException(Object exception, StackTrace stackTrace); - /// Intecepts rendering exceptions, logs them and sends them to Bugsee. + /// Intercepts rendering exceptions, logs them and sends them to Bugsee. Future inteceptRenderingExceptions(FlutterErrorDetails error); } From b87addb4506f9c01d2da9d2931befeeb2504c902 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Thu, 27 Feb 2025 17:00:19 +0100 Subject: [PATCH 13/14] chore: update production env variables --- src/app/.env.prod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/.env.prod b/src/app/.env.prod index 88d164e4..e2c01228 100644 --- a/src/app/.env.prod +++ b/src/app/.env.prod @@ -5,4 +5,4 @@ DAD_JOKES_BASE_URL='https://www.reddit.com/r/dadjokes' APP_STORE_URL_IOS=https://apps.apple.com/us/app/uno-calculator/id1464736591 APP_STORE_URL_Android=https://play.google.com/store/apps/details?id=uno.platform.calculator REMOTE_CONFIG_FETCH_INTERVAL_MINUTES=720 -DIAGNOSTIC_ENABLED=true \ No newline at end of file +DIAGNOSTIC_ENABLED=false \ No newline at end of file From cced807d97f92f42ca5411bd8e767d779b8a11cc Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Mon, 3 Mar 2025 14:41:38 +0100 Subject: [PATCH 14/14] chore: update changelog documentation --- src/cli/CHANGELOG.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cli/CHANGELOG.md b/src/cli/CHANGELOG.md index bb81245b..b996e9bf 100644 --- a/src/cli/CHANGELOG.md +++ b/src/cli/CHANGELOG.md @@ -6,12 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Prefix your items with `(Template)` if the change is about the template and not the resulting application. ## 0.26.0 -- Add bugsee global inteceptor for dart exceptions to template app. -- Add bugsee global inteceptor for flutter layout exceptions to template app. -- Add bugsee custom attributes logging. -- Add bugsee custom events logging. -- Add bugsee custom traces logging. -- Implement log file attach to reported bugsee exceptions. +- Add Bugsee global inteceptor for dart exceptions to template app. +- Add Bugsee global inteceptor for flutter layout exceptions to template app. +- Add Bugsee custom attributes logging. +- Add Bugsee custom events logging. +- Add Bugsee custom traces logging. +- Implement log file attach to reported Bugsee exceptions. - Implement obsucre data feature from reported videos. - Implement Bugsee enable log and log filter features. - Update diagnostic overlay to test Bugsee advanced features. @@ -46,8 +46,8 @@ Prefix your items with `(Template)` if the change is about the template and not - Added conventional commit validation stage `stage-build.yml` ## 0.21.0 -- Add bugsee sdk in Fluttter template -- Update build stage in `steps-build-android.yml` and `steps-build-ios` providing bugsee token +- Add Bugsee sdk in Fluttter template +- Update build stage in `steps-build-android.yml` and `steps-build-ios` providing Bugsee token ## 0.20.4 - Updates to documentation