shitman/lib/services/log_service.dart
zeyus 67aaa9589f
All checks were successful
/ build-web (push) Successful in 4m5s
updated level...player can now "complete" (no ui)
2025-07-27 17:41:51 +02:00

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();
}
}