Skip to content

Commit

Permalink
feat: logging (#787)
Browse files Browse the repository at this point in the history
* feat: LoggingManager implementation

* fix: typo

* refactor: rename LoggingManager to LoggingService

* refactor: make LoggingService global and initialized as first in bootstrap

* fix: typo

* chore: typos
  • Loading branch information
damian-molinski authored Sep 11, 2024
1 parent 94a1193 commit 0e7f02c
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 20 deletions.
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

0 comments on commit 0e7f02c

Please sign in to comment.