258 lines
5.9 KiB
Dart
258 lines
5.9 KiB
Dart
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<LogEntry> _buffer = Queue<LogEntry>();
|
|
final StreamController<LogEntry> _controller =
|
|
StreamController<LogEntry>.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<LogEntry>.from(_buffer);
|
|
_buffer.clear();
|
|
|
|
_fileSendPort!.send(entries);
|
|
}
|
|
|
|
Future<void> 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<dynamic> 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<LogEntry>) {
|
|
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<LogEntry> 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<void> dispose() async {
|
|
_flushTimer?.cancel();
|
|
_flushBuffer();
|
|
await _controller.close();
|
|
_fileIsolate?.kill();
|
|
}
|
|
}
|