- Introduced a new `logSafe` function for consistent logging with sensitivity handling. - Replaced direct logger calls with `logSafe` in `api_service.dart`, `app_initializer.dart`, `auth_service.dart`, `permission_service.dart`, and `my_image_compressor.dart`. - Enhanced error handling and logging in various service methods to capture exceptions and provide more context. - Updated image compression logging to include quality and size metrics. - Improved app initialization logging to capture success and error states. - Ensured sensitive information is not logged directly.
157 lines
4.0 KiB
Dart
157 lines
4.0 KiB
Dart
import 'dart:io';
|
|
import 'package:logger/logger.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
|
|
/// Global logger instance
|
|
late final Logger appLogger;
|
|
|
|
/// Log file output handler
|
|
late final FileLogOutput fileLogOutput;
|
|
|
|
/// Initialize logging (call once in `main()`)
|
|
Future<void> initLogging() async {
|
|
await requestStoragePermission();
|
|
|
|
fileLogOutput = FileLogOutput();
|
|
|
|
appLogger = Logger(
|
|
printer: PrettyPrinter(
|
|
methodCount: 0,
|
|
printTime: true,
|
|
colors: true,
|
|
printEmojis: true,
|
|
),
|
|
output: MultiOutput([
|
|
ConsoleOutput(), // ✅ Console will use the top-level PrettyPrinter
|
|
fileLogOutput, // ✅ File will still use the SimpleFileLogPrinter
|
|
]),
|
|
level: Level.debug,
|
|
);
|
|
}
|
|
|
|
/// Request storage permission (for Android 11+)
|
|
Future<void> requestStoragePermission() async {
|
|
final status = await Permission.manageExternalStorage.status;
|
|
if (!status.isGranted) {
|
|
await Permission.manageExternalStorage.request();
|
|
}
|
|
}
|
|
|
|
/// Safe logger wrapper
|
|
void logSafe(
|
|
String message, {
|
|
LogLevel level = LogLevel.info,
|
|
dynamic error,
|
|
StackTrace? stackTrace,
|
|
bool sensitive = false,
|
|
}) {
|
|
if (sensitive) return;
|
|
|
|
switch (level) {
|
|
case LogLevel.debug:
|
|
appLogger.d(message, error: error, stackTrace: stackTrace);
|
|
break;
|
|
case LogLevel.warning:
|
|
appLogger.w(message, error: error, stackTrace: stackTrace);
|
|
break;
|
|
case LogLevel.error:
|
|
appLogger.e(message, error: error, stackTrace: stackTrace);
|
|
break;
|
|
case LogLevel.verbose:
|
|
appLogger.v(message, error: error, stackTrace: stackTrace);
|
|
break;
|
|
default:
|
|
appLogger.i(message, error: error, stackTrace: stackTrace);
|
|
}
|
|
}
|
|
|
|
/// Custom log output that writes to a local `.txt` file
|
|
class FileLogOutput extends LogOutput {
|
|
File? _logFile;
|
|
|
|
/// Initialize log file in Downloads/marco_logs/log_YYYY-MM-DD.txt
|
|
Future<void> _init() async {
|
|
if (_logFile != null) return;
|
|
|
|
final directory = Directory('/storage/emulated/0/Download/marco_logs');
|
|
if (!await directory.exists()) {
|
|
await directory.create(recursive: true);
|
|
}
|
|
|
|
final date = DateFormat('yyyy-MM-dd').format(DateTime.now());
|
|
final filePath = '${directory.path}/log_$date.txt';
|
|
_logFile = File(filePath);
|
|
|
|
if (!await _logFile!.exists()) {
|
|
await _logFile!.create();
|
|
}
|
|
|
|
await _cleanOldLogs(directory);
|
|
}
|
|
|
|
@override
|
|
void output(OutputEvent event) async {
|
|
await _init();
|
|
|
|
if (event.lines.isEmpty) return;
|
|
|
|
final logMessage = event.lines.join('\n') + '\n';
|
|
await _logFile!.writeAsString(
|
|
logMessage,
|
|
mode: FileMode.append,
|
|
flush: true,
|
|
);
|
|
}
|
|
|
|
Future<String> getLogFilePath() async {
|
|
await _init();
|
|
return _logFile!.path;
|
|
}
|
|
|
|
Future<void> clearLogs() async {
|
|
await _init();
|
|
await _logFile!.writeAsString('');
|
|
}
|
|
|
|
Future<String> readLogs() async {
|
|
await _init();
|
|
return _logFile!.readAsString();
|
|
}
|
|
|
|
/// Delete logs older than 3 days
|
|
Future<void> _cleanOldLogs(Directory directory) async {
|
|
final files = directory.listSync();
|
|
final now = DateTime.now();
|
|
|
|
for (var file in files) {
|
|
if (file is File && file.path.endsWith('.txt')) {
|
|
final stat = await file.stat();
|
|
if (now.difference(stat.modified).inDays > 3) {
|
|
await file.delete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A simple, readable log printer for file output
|
|
class SimpleFileLogPrinter extends LogPrinter {
|
|
@override
|
|
List<String> log(LogEvent event) {
|
|
final message = event.message.toString();
|
|
|
|
if (message.contains('[SENSITIVE]')) return [];
|
|
|
|
final timestamp = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
|
|
final level = event.level.name.toUpperCase();
|
|
final error = event.error != null ? ' | ERROR: ${event.error}' : '';
|
|
final stack =
|
|
event.stackTrace != null ? '\nSTACKTRACE:\n${event.stackTrace}' : '';
|
|
return ['[$timestamp] [$level] $message$error$stack'];
|
|
}
|
|
}
|
|
|
|
/// Optional log level enum for better type safety
|
|
enum LogLevel { debug, info, warning, error, verbose }
|