144 lines
3.5 KiB
Dart

import 'dart:io';
import 'package:logger/logger.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
/// Global logger instance
late final Logger appLogger;
late final FileLogOutput fileLogOutput;
/// Initialize logging
Future<void> initLogging() async {
fileLogOutput = FileLogOutput();
appLogger = Logger(
printer: PrettyPrinter(
methodCount: 0,
printTime: true,
colors: true,
printEmojis: true,
),
output: MultiOutput([
ConsoleOutput(),
fileLogOutput,
]),
level: Level.debug,
);
}
/// 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);
}
}
/// Log output to file (safe path, no permission required)
class FileLogOutput extends LogOutput {
File? _logFile;
Future<void> _init() async {
if (_logFile != null) return;
final baseDir = await getExternalStorageDirectory();
final directory = Directory('${baseDir!.path}/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();
}
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();
}
}
}
}
}
/// Simple 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 enum for log levels
enum LogLevel { debug, info, warning, error, verbose }