Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: logging #787

Merged
merged 8 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions catalyst_voices/lib/configs/app_bloc_observer.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import 'dart:developer';

import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

final class AppBlocObserver extends BlocObserver {
AppBlocObserver();

final _logger = Logger('AppBlocObserver');

@override
void onChange(
BlocBase<dynamic> bloc,
Change<dynamic> change,
) {
super.onChange(bloc, change);
log('onChange(${bloc.runtimeType}, $change)');
_logger.info('onChange(${bloc.runtimeType}, $change)');
}

@override
Expand All @@ -18,7 +21,7 @@ final class AppBlocObserver extends BlocObserver {
Object error,
StackTrace stackTrace,
) {
log('onError(${bloc.runtimeType}, $error, $stackTrace)');
_logger.warning('onError(${bloc.runtimeType})', error, stackTrace);
super.onError(bloc, error, stackTrace);
}
}
74 changes: 59 additions & 15 deletions catalyst_voices/lib/configs/bootstrap.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import 'dart:async';
import 'dart:developer';

import 'package:catalyst_voices/app/app.dart';
import 'package:catalyst_voices/configs/app_bloc_observer.dart';
import 'package:catalyst_voices/configs/sentry_service.dart';
import 'package:catalyst_voices/dependency/dependencies.dart';
import 'package:catalyst_voices/routes/guards/milestone_guard.dart';
import 'package:catalyst_voices/routes/routes.dart';
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:url_strategy/url_strategy.dart';

final _loggingService = LoggingService();

final _bootstrapLogger = Logger('Bootstrap');
final _flutterLogger = Logger('Flutter');
final _platformDispatcherLogger = Logger('PlatformDispatcher');
final _uncaughtZoneLogger = Logger('UncaughtZone');

typedef BootstrapWidgetBuilder = FutureOr<Widget> Function(BootstrapArgs args);

final class BootstrapArgs {
Expand All @@ -23,26 +30,40 @@ final class BootstrapArgs {
});
}

// TODO(damian-molinski): Add PlatformDispatcher.instance.onError
// TODO(damian-molinski): Add Isolate.current.addErrorListener
// TODO(damian-molinski): Add runZonedGuarded
// TODO(damian-molinski): Add Global try-catch
Future<void> bootstrap([
BootstrapWidgetBuilder builder = _appBuilder,
BootstrapWidgetBuilder builder = _defaultBuilder,
]) async {
runZonedGuarded(
() => _safeBootstrap(builder),
_reportUncaughtZoneError,
);
}

Future<void> _safeBootstrap(BootstrapWidgetBuilder builder) async {
try {
await _doBootstrap(builder);
} catch (error, stack) {
await _reportBootstrapError(error, stack);
}
}

Future<void> _doBootstrap(BootstrapWidgetBuilder builder) async {
// There's no need to call WidgetsFlutterBinding.ensureInitialized()
// since this is already done internally by SentryFlutter.init()
// More info here: https://github.com/getsentry/sentry-dart/issues/2063
if (!kReleaseMode) {
WidgetsFlutterBinding.ensureInitialized();
}

FlutterError.onError = (details) {
log(
details.exceptionAsString(),
stackTrace: details.stack,
);
};
_loggingService
..level = kDebugMode ? Level.ALL : Level.OFF
..printLogs = kDebugMode;

FlutterError.onError = _reportFlutterError;
PlatformDispatcher.instance.onError = _reportPlatformDispatcherError;

await Dependencies.instance.init();

GoRouter.optionURLReflectsImperativeAPIs = true;
setPathUrlStrategy();
Expand All @@ -55,11 +76,10 @@ Future<void> bootstrap([

Bloc.observer = AppBlocObserver();

await Dependencies.instance.init();

final args = BootstrapArgs(routerConfig: router);
final app = await builder(args);

await _runApp(await builder(args));
await _runApp(app);
}

Future<void> _runApp(Widget app) async {
Expand All @@ -70,8 +90,32 @@ Future<void> _runApp(Widget app) async {
}
}

Widget _appBuilder(BootstrapArgs args) {
Widget _defaultBuilder(BootstrapArgs args) {
return App(
routerConfig: args.routerConfig,
);
}

Future<void> _reportBootstrapError(Object error, StackTrace stack) async {
_bootstrapLogger.severe('Error while bootstrapping', error, stack);
}

/// Flutter-specific assertion failures and contract violations.
Future<void> _reportFlutterError(FlutterErrorDetails details) async {
_flutterLogger.severe(
details.context?.toStringDeep(),
details.exception,
details.stack,
);
}

/// Platform Dispatcher Errors reporting
bool _reportPlatformDispatcherError(Object error, StackTrace stack) {
_platformDispatcherLogger.severe('Platform Error', error, stack);
return true;
}

/// Uncaught Errors reporting
void _reportUncaughtZoneError(Object error, StackTrace stack) {
_uncaughtZoneLogger.severe('Uncaught Error', error, stack);
}
4 changes: 3 additions & 1 deletion catalyst_voices/lib/routes/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ abstract final class AppRouter {
List<RouteGuard> guards = const [],
}) {
return GoRouter(
debugLogDiagnostics: true,
navigatorKey: _rootNavigatorKey,
initialLocation: Routes.initialLocation,
redirect: (context, state) => _guard(context, state, guards),
observers: [
SentryNavigatorObserver(),
],
routes: Routes.routes,
// always true. We're deciding whether to print
// them or not in LoggingService
debugLogDiagnostics: true,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export 'common/build_config.dart';
export 'common/build_environment.dart';
export 'dependency/dependency_provider.dart';
export 'formatter/cryptocurrency_formatter.dart';
export 'logging/logging_service.dart';
export 'platform/catalyst_platform.dart';
export 'platform_aware_builder/platform_aware_builder.dart';
export 'responsive/responsive_builder.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import 'dart:async';
import 'dart:developer' as developer;

import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';

export 'package:logging/logging.dart' show Level, Logger;

abstract interface class LoggingService {
factory LoggingService() {
return _LoggingServiceImpl(root: Logger.root);
}

set level(Level newValue);

set printLogs(bool newValue);

void dispose();
}

final class _LoggingServiceImpl implements LoggingService {
final Logger root;

StreamSubscription<LogRecord>? _recordsSub;

_LoggingServiceImpl({
required this.root,
}) : _printLogs = false {
hierarchicalLoggingEnabled = true;

root.level = Level.OFF;
_recordsSub = root.onRecord.listen(_onLogRecord);
}

@override
set level(Level newValue) {
if (root.level == newValue) {
return;
}
root.level = newValue;
}

bool get printLogs => _printLogs;

bool _printLogs;

@override
set printLogs(bool newValue) {
if (_printLogs == newValue) {
return;
}
_printLogs = newValue;
}

@override
void dispose() {
_recordsSub?.cancel();
_recordsSub = null;
}

void _onLogRecord(LogRecord log) {
if (_printLogs) {
_printLogRecord(log);
}
}

void _printLogRecord(LogRecord log) {
if (log.level >= Level.SEVERE) {
final error = log.error;
final errorDetails = FlutterErrorDetails(
exception: error is Exception ? error : Exception(error),
stack: log.stackTrace,
library: log.loggerName,
context: ErrorDescription(log.message),
);
FlutterError.dumpErrorToConsole(errorDetails);
return;
}

developer.log(
log.message,
time: log.time,
sequenceNumber: log.sequenceNumber,
level: log.level.value,
name: log.loggerName,
zone: log.zone,
error: log.error,
stackTrace: log.stackTrace,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies:
sdk: flutter
get_it: ^7.6.7
intl: ^0.19.0
logging: ^1.2.0
web: ^0.5.0

dev_dependencies:
Expand Down
1 change: 1 addition & 0 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ command:
flutter_quill: ^10.5.0
flutter_quill_extensions: ^10.5.0
formz: ^0.7.0
logging: ^1.2.0
meta: ^1.10.0
result_type: ^0.2.0
plugin_platform_interface: ^2.1.7
Expand Down
Loading