diff --git a/lib/exceptions/logger.abs.dart b/lib/exceptions/logger.abs.dart new file mode 100644 index 000000000..523f541f6 --- /dev/null +++ b/lib/exceptions/logger.abs.dart @@ -0,0 +1,74 @@ +// ignore_for_file: avoid_equals_and_hash_code_on_mutable_classes +// ignore_for_file: type_annotate_public_apis +// ignore_fir_file: hash_and_equals + +/// Code based on https://pub.dev/packages/logging +class LogLevel implements Comparable { + final String name; + + final int value; + + const LogLevel(this.name, this.value); + + static const LogLevel all = LogLevel('All', 0); + + static const LogLevel off = LogLevel('OFF', 2000); + + static const LogLevel finest = LogLevel('FINEST', 300); + + static const LogLevel finer = LogLevel('FINER', 400); + + static const LogLevel fine = LogLevel('FINE', 500); + + static const LogLevel info = LogLevel('INFO', 800); + + static const LogLevel warning = LogLevel('Warning', 900); + + static const LogLevel severe = LogLevel('Severe', 1000); + + static const List levels = [ + all, + off, + finest, + finer, + fine, + info, + warning, + severe, + ]; + + @override + bool operator ==(Object other) => other is LogLevel && value == other.value; + + bool operator <(Object other) => other is LogLevel && value < other.value; + + bool operator <=(Object other) => other is LogLevel && value <= other.value; + + bool operator >(Object other) => other is LogLevel && value > other.value; + + bool operator >=(Object other) => other is LogLevel && value >= other.value; + + @override + int compareTo(LogLevel other) => value - other.value; + + @override + int get hashCode => value; +} + +abstract class Logging { + static LogLevel level = LogLevel.info; + + void info(message, [Object? error, StackTrace? stackTrace]) {} + + void finest(message, [Object? error, StackTrace? stackTrace]) {} + + void finer(message, [Object? error, StackTrace? stackTrace]) {} + + void fine(message, [Object? error, StackTrace? stackTrace]) {} + + void warning(message, [Object? error, StackTrace? stackTrace]) {} + + void severe(message, [Object? error, StackTrace? stackTrace]) {} + + void provider(message, [Object? error, StackTrace? stackTrace]) {} +} diff --git a/lib/exceptions/logger_adaptor.dart b/lib/exceptions/logger_adaptor.dart new file mode 100644 index 000000000..b9b314e6f --- /dev/null +++ b/lib/exceptions/logger_adaptor.dart @@ -0,0 +1,140 @@ +// ignore_for_file: type_annotate_public_apis + +import 'dart:developer'; +import 'package:flutter/foundation.dart' as foundation; +import 'package:logging/logging.dart'; +import 'package:wger/exceptions/logger.abs.dart'; + +class LogInfo { + final String log; + final StackTrace? trace; + + LogInfo(this.log, this.trace); +} + +class LoggingAdaptor extends Logging { + late Logger _logger; + + LoggingAdaptor(String name) { + Logger.root.level = _level(Logging.level); + _logger = Logger(name); + } + + /// Listen for logs being added to the logging stream and + /// print them out as needed. An equivalent function can be + /// created in other logging classes using this stream + static void listenForLogs() { + Logger.root.onRecord.listen((record) { + if (Logging.level != LogLevel.off) { + final parsedLog = _parse( + message: record.message, + level: record.level, + loggerName: record.loggerName, + ); + + log(parsedLog); + } + }); + } + + static String _parse({ + required message, + required Level level, + required String loggerName, + Object? error, + }) { + String? callerInfo; + try { + if (level >= Level.WARNING) { + final lines = StackTrace.current.toString().split('\n'); + final currentTrace = lines[2]; + final indexOfWhiteSpace = currentTrace.indexOf(' '); + + callerInfo = currentTrace.substring(indexOfWhiteSpace).trim(); + } + } catch (_) {} + + final traceInfo = callerInfo != null ? '| $callerInfo ' : ''; + final logger = loggerName != '' ? loggerName : 'Default'; + + return '[$logger] ${level.name}: $message ${error ?? ''} $traceInfo'; + } + + @override + void fine(message, [Object? error, StackTrace? stackTrace]) { + if (LogLevel.fine >= Logging.level) { + _logger.fine(message, error, stackTrace); + } + } + + @override + void finer(message, [Object? error, StackTrace? stackTrace]) { + if (LogLevel.fine >= Logging.level) { + _logger.finer(message, error, stackTrace); + } + } + + @override + void finest(message, [Object? error, StackTrace? stackTrace]) { + if (LogLevel.fine >= Logging.level) { + _logger.finest(message, error, stackTrace); + } + } + + @override + void info(message, [Object? error, StackTrace? stackTrace]) { + if (LogLevel.fine >= Logging.level) { + _logger.info(message, error, stackTrace); + } + } + + @override + void severe(message, [Object? error, StackTrace? stackTrace]) { + if (LogLevel.fine >= Logging.level) { + _logger.severe(message, error, stackTrace); + } + } + + @override + void warning(message, [Object? error, StackTrace? stackTrace]) { + if (LogLevel.warning >= Logging.level) { + _logger.warning(message, error, stackTrace); + } + } + + static Level _level(LogLevel logLevel) { + // Disable local log output if app is in release mode + if (foundation.kReleaseMode) { + return Level.OFF; + } + + switch (logLevel) { + case LogLevel.all: + return Level.ALL; + + case LogLevel.fine: + return Level.FINE; + + case LogLevel.finer: + return Level.FINER; + + case LogLevel.finest: + return Level.FINEST; + + case LogLevel.off: + return Level.OFF; + + case LogLevel.info: + return Level.INFO; + + case LogLevel.warning: + return Level.WARNING; + + case LogLevel.severe: + return Level.SEVERE; + + default: + return Level.OFF; + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 88240ae3b..a3307020b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,6 +20,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:wger/core/locator.dart'; +import 'package:wger/exceptions/logger.abs.dart'; +import 'package:wger/exceptions/logger_adaptor.dart'; import 'package:wger/providers/add_exercise.dart'; import 'package:wger/providers/base_provider.dart'; import 'package:wger/providers/body_weight.dart'; @@ -50,6 +52,7 @@ import 'package:wger/screens/workout_plans_screen.dart'; import 'package:wger/theme/theme.dart'; import 'package:wger/widgets/core/about.dart'; import 'package:wger/widgets/core/settings.dart'; +import 'package:flutter/foundation.dart' as foundation; import 'providers/auth.dart'; @@ -61,6 +64,12 @@ void main() async { // Locator to initialize exerciseDB await ServiceLocator().configure(); + + // Set the LogLevel of the app depending on if the app is in release + // mode or not. + Logging.level = foundation.kReleaseMode ? LogLevel.off : LogLevel.fine; + LoggingAdaptor.listenForLogs(); + // Application runApp(MyApp()); } diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 04957f7a1..b3dc684fa 100644 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -30,6 +30,8 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:version/version.dart'; import 'package:wger/exceptions/http_exception.dart'; +import 'package:wger/exceptions/logger.abs.dart'; +import 'package:wger/exceptions/logger_adaptor.dart'; import 'package:wger/helpers/consts.dart'; import 'helpers.dart'; @@ -40,6 +42,8 @@ enum LoginActions { } class AuthProvider with ChangeNotifier { + late final Logging logger = LoggingAdaptor('AuthProvider'); + String? token; String? serverUrl; String? serverVersion; @@ -83,6 +87,7 @@ class AuthProvider with ChangeNotifier { /// Server application version Future setServerVersion() async { final response = await client.get(makeUri(serverUrl!, SERVER_VERSION_URL)); + logger.info('Server version set successfully'); serverVersion = json.decode(response.body); } diff --git a/lib/screens/home_tabs_screen.dart b/lib/screens/home_tabs_screen.dart index bca518c86..87aabee2f 100644 --- a/lib/screens/home_tabs_screen.dart +++ b/lib/screens/home_tabs_screen.dart @@ -23,6 +23,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:rive/rive.dart'; +import 'package:wger/exceptions/logger.abs.dart'; +import 'package:wger/exceptions/logger_adaptor.dart'; import 'package:wger/providers/auth.dart'; import 'package:wger/providers/body_weight.dart'; import 'package:wger/providers/exercises.dart'; @@ -45,7 +47,9 @@ class HomeTabsScreen extends StatefulWidget { } class _HomeTabsScreenState extends State with SingleTickerProviderStateMixin { + late final Logging logger = LoggingAdaptor('HomeTabsScreen'); late Future _initialData; + int _selectedIndex = 0; @override @@ -83,7 +87,7 @@ class _HomeTabsScreenState extends State with SingleTickerProvid final userProvider = context.read(); // Base data - log('Loading base data'); + logger.info('Loading base data'); try { await Future.wait([ authProvider.setServerVersion(), diff --git a/pubspec.yaml b/pubspec.yaml index c2852e7dd..5fbf9a4fd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,7 @@ dependencies: flex_color_scheme: ^7.3.1 freezed_annotation: ^2.4.1 clock: ^1.1.1 + logging: ^1.2.0 dev_dependencies: flutter_test: