From 4d9287ae55cb19352a2a9f9e61749dded0c5a674 Mon Sep 17 00:00:00 2001 From: Badr Kouki Date: Mon, 18 Nov 2024 15:03:03 +0100 Subject: [PATCH 01/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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),