1
0
mirror of https://github.com/immich-app/immich.git synced 2025-08-09 23:17:29 +02:00

refactor: logger service and remove dynamic (#17733)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong
2025-06-09 08:31:31 +05:30
committed by GitHub
parent 7b2237b86b
commit 2d05a5482f
13 changed files with 119 additions and 140 deletions

View File

@@ -8,6 +8,11 @@ import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/store.model.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 ILogRepository _logRepository;
final IStoreRepository _storeRepository;
@@ -18,19 +23,11 @@ class LogService {
/// 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;
LogService._(
this._logRepository,
this._storeRepository,
this._shouldBuffer,
) {
// Listen to log messages and write them to the database
_logSubscription = Logger.root.onRecord.listen(_writeLogToDatabase);
}
static LogService? _instance;
static LogService get I {
if (_instance == null) {
@@ -44,10 +41,7 @@ class LogService {
required IStoreRepository storeRepository,
bool shouldBuffer = true,
}) async {
if (_instance != null) {
return _instance!;
}
_instance = await create(
_instance ??= await create(
logRepository: logRepository,
storeRepository: storeRepository,
shouldBuffer: shouldBuffer,
@@ -61,55 +55,28 @@ class LogService {
bool shouldBuffer = true,
}) async {
final instance = LogService._(logRepository, storeRepository, shouldBuffer);
// Truncate logs to 250
await logRepository.truncate(limit: kLogTruncateLimit);
// Get log level from store
final level = await instance._storeRepository.tryGet(StoreKey.logLevel);
if (level != null) {
Logger.root.level = Level.LEVELS.elementAtOrNull(level) ?? Level.INFO;
}
final level = await instance._storeRepository.tryGet(StoreKey.logLevel) ??
LogLevel.info.index;
Logger.root.level = Level.LEVELS.elementAtOrNull(level) ?? Level.INFO;
return instance;
}
Future<void> setlogLevel(LogLevel level) async {
await _storeRepository.insert(StoreKey.logLevel, level.index);
Logger.root.level = level.toLevel();
LogService._(
this._logRepository,
this._storeRepository,
this._shouldBuffer,
) {
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
}
Future<List<LogMessage>> getMessages() async {
final logsFromDb = await _logRepository.getAll();
if (_msgBuffer.isNotEmpty) {
return [..._msgBuffer.reversed, ...logsFromDb];
}
return logsFromDb;
}
Future<void> clearLogs() async {
_flushTimer?.cancel();
_flushTimer = null;
_msgBuffer.clear();
await _logRepository.deleteAll();
}
/// Flush pending log messages to persistent storage
void flush() {
if (_flushTimer == null) {
return;
}
_flushTimer!.cancel();
// TODO: Rename enable this after moving to sqlite - #16504
// await _flushBufferToDatabase();
}
Future<void> dispose() {
_flushTimer?.cancel();
_logSubscription.cancel();
return _flushBufferToDatabase();
}
void _writeLogToDatabase(LogRecord r) {
void _handleLogRecord(LogRecord r) {
if (kDebugMode) {
debugPrint('[${r.level.name}] [${r.time}] ${r.message}');
debugPrint(
'[${r.level.name}] [${r.time}] [${r.loggerName}] ${r.message}'
'${r.error == null ? '' : '\nError: ${r.error}'}'
'${r.stackTrace == null ? '' : '\nStack: ${r.stackTrace}'}',
);
}
final record = LogMessage(
@@ -125,14 +92,44 @@ class LogService {
_msgBuffer.add(record);
_flushTimer ??= Timer(
const Duration(seconds: 5),
() => unawaited(_flushBufferToDatabase()),
() => unawaited(flushBuffer()),
);
} else {
unawaited(_logRepository.insert(record));
}
}
Future<void> _flushBufferToDatabase() async {
Future<void> setLogLevel(LogLevel level) async {
await _storeRepository.insert(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();
}
void flush() {
_flushTimer?.cancel();
// TODO: Rename enable this after moving to sqlite - #16504
// await _flushBufferToDatabase();
}
Future<void> dispose() {
_flushTimer?.cancel();
_logSubscription.cancel();
return flushBuffer();
}
// TOOD: Move this to private once Isar is removed
Future<void> flushBuffer() async {
_flushTimer = null;
final buffer = [..._msgBuffer];
_msgBuffer.clear();