mirror of
https://github.com/immich-app/immich.git
synced 2025-11-01 19:17:12 -04:00
* add lint rule * update usages * stragglers * use dcm * formatting * test ci * Revert "test ci" This reverts commit 8f864c4e4d3a7ec1a7e820b1afb3e801f2e82bc5. * revert whitespace change
149 lines
4.4 KiB
Dart
149 lines
4.4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:immich_mobile/constants/constants.dart';
|
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
|
import 'package:immich_mobile/utils/debug_print.dart';
|
|
import 'package:logging/logging.dart';
|
|
|
|
/// Service responsible for handling application logging.
|
|
///
|
|
/// It listens to Dart's [Logger.root], buffers logs in memory (optionally),
|
|
/// writes them to a persistent [ILogRepository], and manages log levels
|
|
/// via [IStoreRepository]
|
|
class LogService {
|
|
final LogRepository _logRepository;
|
|
final IStoreRepository _storeRepository;
|
|
|
|
final List<LogMessage> _msgBuffer = [];
|
|
|
|
/// Whether to buffer logs in memory before writing to the database.
|
|
/// This is useful when logging in quick succession, as it increases performance
|
|
/// and reduces NAND wear. However, it may cause the logs to be lost in case of a crash / in isolates.
|
|
final bool _shouldBuffer;
|
|
|
|
Timer? _flushTimer;
|
|
|
|
late final StreamSubscription<LogRecord> _logSubscription;
|
|
|
|
static LogService? _instance;
|
|
static LogService get I {
|
|
if (_instance == null) {
|
|
throw const LoggerUnInitializedException();
|
|
}
|
|
return _instance!;
|
|
}
|
|
|
|
static Future<LogService> init({
|
|
required LogRepository logRepository,
|
|
required IStoreRepository storeRepository,
|
|
bool shouldBuffer = true,
|
|
}) async {
|
|
_instance ??= await create(
|
|
logRepository: logRepository,
|
|
storeRepository: storeRepository,
|
|
shouldBuffer: shouldBuffer,
|
|
);
|
|
return _instance!;
|
|
}
|
|
|
|
static Future<LogService> create({
|
|
required LogRepository logRepository,
|
|
required IStoreRepository storeRepository,
|
|
bool shouldBuffer = true,
|
|
}) async {
|
|
final instance = LogService._(logRepository, storeRepository, shouldBuffer);
|
|
await logRepository.truncate(limit: kLogTruncateLimit);
|
|
final level = await instance._storeRepository.tryGet(StoreKey.logLevel) ?? LogLevel.info.index;
|
|
Logger.root.level = Level.LEVELS.elementAtOrNull(level) ?? Level.INFO;
|
|
return instance;
|
|
}
|
|
|
|
LogService._(this._logRepository, this._storeRepository, this._shouldBuffer) {
|
|
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
|
|
}
|
|
|
|
void _handleLogRecord(LogRecord r) {
|
|
dPrint(
|
|
() =>
|
|
'[${r.level.name}] [${r.time}] [${r.loggerName}] ${r.message}'
|
|
'${r.error == null ? '' : '\nError: ${r.error}'}'
|
|
'${r.stackTrace == null ? '' : '\nStack: ${r.stackTrace}'}',
|
|
);
|
|
|
|
final record = LogMessage(
|
|
message: r.message,
|
|
level: r.level.toLogLevel(),
|
|
createdAt: r.time,
|
|
logger: r.loggerName,
|
|
error: r.error?.toString(),
|
|
stack: r.stackTrace?.toString(),
|
|
);
|
|
|
|
if (_shouldBuffer) {
|
|
_msgBuffer.add(record);
|
|
_flushTimer ??= Timer(const Duration(seconds: 5), () => unawaited(_flushBuffer()));
|
|
} else {
|
|
unawaited(_logRepository.insert(record));
|
|
}
|
|
}
|
|
|
|
Future<void> setLogLevel(LogLevel level) async {
|
|
await _storeRepository.upsert(StoreKey.logLevel, level.index);
|
|
Logger.root.level = level.toLevel();
|
|
}
|
|
|
|
Future<List<LogMessage>> getMessages() async {
|
|
final logsFromDb = await _logRepository.getAll();
|
|
return [..._msgBuffer.reversed, ...logsFromDb];
|
|
}
|
|
|
|
Future<void> clearLogs() async {
|
|
_flushTimer?.cancel();
|
|
_flushTimer = null;
|
|
_msgBuffer.clear();
|
|
await _logRepository.deleteAll();
|
|
}
|
|
|
|
Future<void> flush() {
|
|
_flushTimer?.cancel();
|
|
return _flushBuffer();
|
|
}
|
|
|
|
Future<void> dispose() {
|
|
_flushTimer?.cancel();
|
|
_logSubscription.cancel();
|
|
return _flushBuffer();
|
|
}
|
|
|
|
Future<void> _flushBuffer() async {
|
|
_flushTimer = null;
|
|
final buffer = [..._msgBuffer];
|
|
_msgBuffer.clear();
|
|
|
|
if (buffer.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
await _logRepository.insertAll(buffer);
|
|
}
|
|
}
|
|
|
|
class LoggerUnInitializedException implements Exception {
|
|
const LoggerUnInitializedException();
|
|
|
|
@override
|
|
String toString() => 'Logger is not initialized. Call init()';
|
|
}
|
|
|
|
/// Log levels according to dart logging [Level]
|
|
extension LevelDomainToInfraExtension on Level {
|
|
LogLevel toLogLevel() => LogLevel.values.elementAtOrNull(Level.LEVELS.indexOf(this)) ?? LogLevel.info;
|
|
}
|
|
|
|
extension on LogLevel {
|
|
Level toLevel() => Level.LEVELS.elementAtOrNull(index) ?? Level.INFO;
|
|
}
|