TF-4136 refactor(logger): unify logging format and route error reporting

This commit is contained in:
dab246
2025-12-12 18:15:17 +07:00
committed by Dat H. Pham
parent 060416292f
commit 73dcb6067c
+198 -53
View File
@@ -1,70 +1,201 @@
import 'dart:async'; import 'dart:async';
import 'package:universal_html/html.dart' as html;
import 'package:core/utils/build_utils.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
final logHistory = _Dispatcher(''); /// ANSI escape colors (Web only)
const ansiReset = '\x1B[0m';
const ansiRed = '\x1B[31m';
const ansiYellow = '\x1B[33m';
const ansiGreen = '\x1B[32m';
const ansiBlue = '\x1B[34m';
const ansiBold = '\x1B[1m';
void log(String? value, {Level level = Level.info}) { const appLogName = '[TwakeMail]';
if (!kDebugMode) return;
String logsStr = value ?? ''; String _applyWebColor(Level level, String text) {
logHistory.value = '$logsStr\n${logHistory.value}'; switch (level) {
case Level.wtf:
if (PlatformInfo.isWeb) { return '$ansiRed$ansiBold!!!CRITICAL!!! $text$ansiReset';
switch (level) { case Level.error:
case Level.wtf: return '$ansiRed$text$ansiReset';
logsStr = '\x1B[31m!!!CRITICAL!!! $logsStr\x1B[0m'; case Level.warning:
break; return '$ansiYellow$text$ansiReset';
case Level.error: case Level.info:
logsStr = '\x1B[31m$logsStr\x1B[0m'; return '$ansiGreen$text$ansiReset';
break; case Level.debug:
case Level.warning: return '$ansiBlue$text$ansiReset';
logsStr = '\x1B[33m$logsStr\x1B[0m'; case Level.verbose:
break; return text;
case Level.info:
logsStr = '\x1B[32m$logsStr\x1B[0m';
break;
case Level.debug:
logsStr = '\x1B[34m$logsStr\x1B[0m';
break;
case Level.verbose:
break;
}
} else {
switch (level) {
case Level.error:
logsStr = '[ERROR] $logsStr';
break;
default:
break;
}
} }
}
String _applyMobileFormat(Level level, String text) {
switch (level) {
case Level.wtf:
return '🔥 CRITICAL: $text';
case Level.error:
return '❌ ERROR: $text';
case Level.warning:
return '⚠️ WARNING: $text';
case Level.info:
return '️ INFO: $text';
case Level.debug:
return '🐛 DEBUG: $text';
case Level.verbose:
return '🔍 VERBOSE: $text';
}
}
void _internalLog(
String? message, {
required Level level,
Object? exception,
StackTrace? stackTrace,
Map<String, dynamic>? extras,
bool webConsoleEnabled = false,
}) {
final rawMessage = _buildRawMessage(message, exception, extras, stackTrace);
final formattedMessage = _formatMessage(level, rawMessage);
if (webConsoleEnabled && PlatformInfo.isWeb) {
_printWebConsole(level, formattedMessage);
} else {
_debugPrint(formattedMessage);
}
}
String _buildRawMessage(
String? message,
Object? exception,
Map<String, dynamic>? extras,
StackTrace? stackTrace,
) {
final parts = <String>[];
if (message?.isNotEmpty == true) parts.add(message!);
if (exception != null) parts.add('exception: $exception');
if (extras != null && extras.isNotEmpty) parts.add('extras: $extras');
if (stackTrace != null) parts.add('stackTrace: $stackTrace');
return parts.join(' | ');
}
String _formatMessage(Level level, String raw) {
return PlatformInfo.isWeb
? _applyWebColor(level, raw)
: _applyMobileFormat(level, raw);
}
void _debugPrint(String formatted) {
if (!BuildUtils.isDebugMode) return;
// ignore: avoid_print // ignore: avoid_print
print('[TwakeMail] $logsStr'); print('$appLogName $formatted');
} }
void logError(String? value) => log(value, level: Level.error); void _printWebConsole(Level level, String value) {
switch (level) {
// Take from: https://flutter.dev/docs/testing/errors case Level.error:
void initLogger(VoidCallback runApp) { case Level.wtf:
runZonedGuarded(() async { html.window.console.error('$appLogName $value');
WidgetsFlutterBinding.ensureInitialized(); break;
FlutterError.onError = (details) { case Level.warning:
FlutterError.dumpErrorToConsole(details); html.window.console.warn('$appLogName $value');
logError('AppLogger::initLogger::runZonedGuarded:FlutterError.onError: ${details.stack.toString()}'); break;
}; case Level.info:
runApp.call(); html.window.console.info('$appLogName $value');
}, (error, stack) { break;
logError('AppLogger::initLogger::runZonedGuarded:onError: $error | stack: $stack'); case Level.debug:
}); case Level.verbose:
html.window.console.debug('$appLogName $value');
break;
}
} }
class _Dispatcher extends ValueNotifier<String> { void logError(
_Dispatcher(String value) : super(value); String? message, {
Object? exception,
StackTrace? stackTrace,
Map<String, dynamic>? extras,
bool webConsoleEnabled = false,
}) {
_internalLog(
message,
level: Level.error,
exception: exception,
stackTrace: stackTrace,
extras: extras,
webConsoleEnabled: webConsoleEnabled,
);
} }
void logWTF(
String? message, {
Object? exception,
StackTrace? stackTrace,
Map<String, dynamic>? extras,
bool webConsoleEnabled = false,
}) {
_internalLog(
message,
level: Level.wtf,
exception: exception,
stackTrace: stackTrace,
extras: extras,
webConsoleEnabled: webConsoleEnabled,
);
}
void logWarning(
String? message, {
bool webConsoleEnabled = false,
}) {
_internalLog(message,
level: Level.warning, webConsoleEnabled: webConsoleEnabled);
}
void logInfo(
String? message, {
bool webConsoleEnabled = false,
}) {
_internalLog(
message,
level: Level.info,
webConsoleEnabled: webConsoleEnabled,
);
}
void logDebug(
String? message, {
bool webConsoleEnabled = false,
}) {
_internalLog(
message,
level: Level.debug,
webConsoleEnabled: webConsoleEnabled,
);
}
void logVerbose(
String? message, {
bool webConsoleEnabled = false,
}) {
_internalLog(
message,
level: Level.verbose,
webConsoleEnabled: webConsoleEnabled,
);
}
void log(
String? message, {
bool webConsoleEnabled = false,
}) =>
logInfo(
message,
webConsoleEnabled: webConsoleEnabled,
);
enum Level { enum Level {
wtf, wtf,
@@ -74,3 +205,17 @@ enum Level {
debug, debug,
verbose, verbose,
} }
// Take from: https://flutter.dev/docs/testing/errors
void initLogger(VoidCallback runApp) {
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (details) {
FlutterError.dumpErrorToConsole(details);
logWarning('AppLogger::initLogger::runZonedGuarded:FlutterError.onError: ${details.stack.toString()}');
};
runApp.call();
}, (error, stack) {
logWarning('AppLogger::initLogger::runZonedGuarded:onError: $error | stack: $stack');
});
}