120 lines
3.0 KiB
Dart
120 lines
3.0 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:injectable/injectable.dart';
|
|
import 'package:moonwell_launcher/config.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
@lazySingleton
|
|
class LauncherLogService {
|
|
static Future<void> _pendingWrite = Future<void>.value();
|
|
|
|
Future<void> info(
|
|
String message, {
|
|
String? installationDir,
|
|
Map<String, Object?> details = const <String, Object?>{},
|
|
}) {
|
|
return _write(
|
|
level: 'INFO',
|
|
message: message,
|
|
installationDir: installationDir,
|
|
details: details,
|
|
);
|
|
}
|
|
|
|
Future<void> error(
|
|
String message, {
|
|
String? installationDir,
|
|
Map<String, Object?> details = const <String, Object?>{},
|
|
Object? error,
|
|
StackTrace? stackTrace,
|
|
}) {
|
|
return _write(
|
|
level: 'ERROR',
|
|
message: message,
|
|
installationDir: installationDir,
|
|
details: details,
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
);
|
|
}
|
|
|
|
Future<String> resolveLogPath({String? installationDir}) async {
|
|
final logDirectory =
|
|
installationDir == null || installationDir.trim().isEmpty
|
|
? p.join(Directory.systemTemp.path, 'moonwell_launcher')
|
|
: p.join(installationDir, Config.launcherMetadataDirectoryName);
|
|
|
|
await Directory(logDirectory).create(recursive: true);
|
|
return p.join(logDirectory, Config.launcherLogFileName);
|
|
}
|
|
|
|
String logHint([String? installationDir]) {
|
|
if (installationDir == null || installationDir.trim().isEmpty) {
|
|
return Config.launcherLogFileName;
|
|
}
|
|
|
|
return '${Config.launcherMetadataDirectoryName}/${Config.launcherLogFileName}';
|
|
}
|
|
|
|
Future<void> _write({
|
|
required String level,
|
|
required String message,
|
|
required String? installationDir,
|
|
required Map<String, Object?> details,
|
|
Object? error,
|
|
StackTrace? stackTrace,
|
|
}) {
|
|
final operation = _pendingWrite.then((_) async {
|
|
final logPath = await resolveLogPath(installationDir: installationDir);
|
|
final buffer = StringBuffer()
|
|
..writeln('[${DateTime.now().toIso8601String()}] [$level] $message');
|
|
|
|
for (final entry in details.entries) {
|
|
if (entry.value == null) {
|
|
continue;
|
|
}
|
|
|
|
buffer.writeln('${entry.key}: ${_stringify(entry.value)}');
|
|
}
|
|
|
|
if (error != null) {
|
|
buffer.writeln('error: ${_stringify(error)}');
|
|
}
|
|
|
|
if (stackTrace != null) {
|
|
buffer.writeln('stackTrace: $stackTrace');
|
|
}
|
|
|
|
buffer.writeln();
|
|
await File(
|
|
logPath,
|
|
).writeAsString(buffer.toString(), mode: FileMode.append, flush: true);
|
|
});
|
|
|
|
_pendingWrite = operation.catchError((_) {});
|
|
return operation;
|
|
}
|
|
|
|
String _stringify(Object? value) {
|
|
if (value == null) {
|
|
return 'null';
|
|
}
|
|
|
|
if (value is String) {
|
|
return value;
|
|
}
|
|
|
|
if (value is Map || value is Iterable) {
|
|
try {
|
|
return jsonEncode(value);
|
|
} catch (_) {
|
|
return value.toString();
|
|
}
|
|
}
|
|
|
|
return value.toString();
|
|
}
|
|
}
|