import 'dart:async'; import 'dart:collection'; import 'dart:io'; import 'dart:isolate'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; mixin class AppLogging { final LogService appLog = LogService(); } class LogEntry { final DateTime timestamp; final Level level; final String message; final String? logger; final Object? error; final StackTrace? stackTrace; LogEntry({ required this.timestamp, required this.level, required this.message, this.logger, this.error, this.stackTrace, }); String format() { final buffer = StringBuffer(); buffer.write('${timestamp.toIso8601String()} '); buffer.write('[${level.name.toUpperCase().padRight(8)}] '); if (logger != null) buffer.write('$logger: '); buffer.write(message); if (error != null) buffer.write(' | Error: $error'); if (stackTrace != null) buffer.write('\n$stackTrace'); return buffer.toString(); } } class LogService { static final LogService _instance = LogService._internal(); static LogService get instance => _instance; static const String defaultLoggerName = 'ShitMan-Game'; final Queue _buffer = Queue(); final StreamController _controller = StreamController.broadcast(); Timer? _flushTimer; Isolate? _fileIsolate; SendPort? _fileSendPort; bool _isFileLoggingEnabled = false; String? _logFilePath; Level _minLevel = Level.INFO; static const int _bufferSize = 100; static const Duration _flushInterval = Duration(seconds: 1); factory LogService() => _instance; LogService._internal() { _initializeLogger(); _setupPeriodicFlush(); } void _initializeLogger() { Logger.root.level = Level.ALL; Logger.root.onRecord.listen((record) { final level = record.level; if (level >= _minLevel) { _addEntry( LogEntry( timestamp: record.time, level: level, message: record.message, logger: record.loggerName, error: record.error, stackTrace: record.stackTrace, ), ); } }); } void _setupPeriodicFlush() { _flushTimer = Timer.periodic(_flushInterval, (_) => _flushBuffer()); } void _addEntry(LogEntry entry) { _buffer.add(entry); _controller.add(entry); if (kDebugMode) { debugPrint(entry.format()); } if (_buffer.length >= _bufferSize) { _flushBuffer(); } } void _flushBuffer() { if (_buffer.isEmpty || !_isFileLoggingEnabled || _fileSendPort == null) { return; } final entries = List.from(_buffer); _buffer.clear(); _fileSendPort!.send(entries); } Future enableFileLogging({String? customPath}) async { if (_isFileLoggingEnabled) return; try { _logFilePath = customPath ?? 'logs/shitman.log'; final receivePort = ReceivePort(); _fileIsolate = await Isolate.spawn(_fileLoggingIsolate, [ receivePort.sendPort, _logFilePath!, ]); _fileSendPort = await receivePort.first as SendPort; _isFileLoggingEnabled = true; } catch (e) { debugPrint('Failed to enable file logging: $e'); } } static void _fileLoggingIsolate(List args) async { final SendPort mainSendPort = args[0]; final String logFilePath = args[1]; final receivePort = ReceivePort(); mainSendPort.send(receivePort.sendPort); IOSink? logFile; try { final file = File(logFilePath); await file.parent.create(recursive: true); logFile = file.openWrite(mode: FileMode.append); await for (final data in receivePort) { if (data is List) { for (final entry in data) { logFile.writeln(entry.format()); } await logFile.flush(); } } } catch (e) { debugPrint('File logging isolate error: $e'); } finally { await logFile?.close(); } } void setMinLevel(Level level) { _minLevel = level; } Stream get logStream => _controller.stream; void log( String message, { Level level = Level.INFO, String? logger, Object? error, StackTrace? stackTrace, }) { Logger(logger ?? defaultLoggerName).log(level, message, error, stackTrace); } void finest( String message, { String? logger, Object? error, StackTrace? stackTrace, }) { Logger(logger ?? defaultLoggerName).finest(message, error, stackTrace); } void finer( String message, { String? logger, Object? error, StackTrace? stackTrace, }) { Logger(logger ?? defaultLoggerName).finer(message, error, stackTrace); } void fine( String message, { String? logger, Object? error, StackTrace? stackTrace, }) { Logger(logger ?? defaultLoggerName).fine(message, error, stackTrace); } void config( String message, { String? logger, Object? error, StackTrace? stackTrace, }) { Logger(logger ?? defaultLoggerName).config(message, error, stackTrace); } void info( String message, { String? logger, Object? error, StackTrace? stackTrace, }) { Logger(logger ?? defaultLoggerName).info(message, error, stackTrace); } void warning( String message, { String? logger, Object? error, StackTrace? stackTrace, }) { Logger(logger ?? defaultLoggerName).warning(message, error, stackTrace); } void severe( String message, { String? logger, Object? error, StackTrace? stackTrace, }) { Logger(logger ?? defaultLoggerName).severe(message, error, stackTrace); } void shout( String message, { String? logger, Object? error, StackTrace? stackTrace, }) { Logger(logger ?? defaultLoggerName).shout(message, error, stackTrace); } Future dispose() async { _flushTimer?.cancel(); _flushBuffer(); await _controller.close(); _fileIsolate?.kill(); } }