From d500ef77cfa697509f13d10b87ba307a0914b922 Mon Sep 17 00:00:00 2001 From: Fynn Petersen-Frey <10599762+fyfrey@users.noreply.github.com> Date: Fri, 14 Apr 2023 15:50:46 +0200 Subject: [PATCH] feature(mobile): configurable log level (#2248) * feature(mobile): configurable log level * increase maxLogEntries to 500 --------- Co-authored-by: Fynn Petersen-Frey --- .../services/app_settings.service.dart | 9 ++--- .../advanced_settings/advanced_settings.dart | 31 ++++++++++++++++ mobile/lib/shared/models/store.dart | 1 + .../services/immich_logger.service.dart | 8 +++- mobile/lib/shared/services/sync.service.dart | 37 +++++++++++++++---- mobile/lib/utils/migration.dart | 4 +- 6 files changed, 73 insertions(+), 17 deletions(-) diff --git a/mobile/lib/modules/settings/services/app_settings.service.dart b/mobile/lib/modules/settings/services/app_settings.service.dart index dbe2652b63..43b4b142d1 100644 --- a/mobile/lib/modules/settings/services/app_settings.service.dart +++ b/mobile/lib/modules/settings/services/app_settings.service.dart @@ -43,17 +43,14 @@ enum AppSettingsEnum { "selectedAlbumSortOrder", 0, ), - advancedTroubleshooting( - StoreKey.advancedTroubleshooting, - "advancedTroubleshooting", - false, - ), + advancedTroubleshooting(StoreKey.advancedTroubleshooting, null, false), + logLevel(StoreKey.logLevel, null, 5) // Level.INFO = 5 ; const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue); final StoreKey storeKey; - final String hiveKey; + final String? hiveKey; final T defaultValue; } diff --git a/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart b/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart index ae9f6c96ba..ee548d1c88 100644 --- a/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart +++ b/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart @@ -5,6 +5,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/modules/settings/ui/settings_switch_list_tile.dart'; +import 'package:immich_mobile/shared/services/immich_logger.service.dart'; +import 'package:logging/logging.dart'; class AdvancedSettings extends HookConsumerWidget { const AdvancedSettings({super.key}); @@ -13,16 +15,21 @@ class AdvancedSettings extends HookConsumerWidget { final appSettingService = ref.watch(appSettingsServiceProvider); final isEnabled = useState(AppSettingsEnum.advancedTroubleshooting.defaultValue); + final levelId = useState(AppSettingsEnum.logLevel.defaultValue); useEffect( () { isEnabled.value = appSettingService.getSetting( AppSettingsEnum.advancedTroubleshooting, ); + levelId.value = appSettingService.getSetting(AppSettingsEnum.logLevel); return null; }, [], ); + + final logLevel = Level.LEVELS[levelId.value].name; + return ExpansionTile( textColor: Theme.of(context).primaryColor, title: const Text( @@ -46,6 +53,30 @@ class AdvancedSettings extends HookConsumerWidget { title: "advanced_settings_troubleshooting_title".tr(), subtitle: "advanced_settings_troubleshooting_subtitle".tr(), ), + ListTile( + dense: true, + title: Text( + // Not translated because the levels are only English + "Log level: $logLevel", + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Slider( + value: levelId.value.toDouble(), + onChanged: (double v) => levelId.value = v.toInt(), + onChangeEnd: (double v) { + appSettingService.setSetting( + AppSettingsEnum.logLevel, + v.toInt(), + ); + ImmichLogger().level = Level.LEVELS[v.toInt()]; + }, + max: 8, + min: 1.0, + divisions: 7, + label: logLevel, + activeColor: Theme.of(context).primaryColor, + ), + ), ], ); } diff --git a/mobile/lib/shared/models/store.dart b/mobile/lib/shared/models/store.dart index 6c73028498..201ffc5a2d 100644 --- a/mobile/lib/shared/models/store.dart +++ b/mobile/lib/shared/models/store.dart @@ -168,6 +168,7 @@ enum StoreKey { albumThumbnailCacheSize(112, type: int), selectedAlbumSortOrder(113, type: int), advancedTroubleshooting(114, type: bool), + logLevel(115, type: int), ; const StoreKey( diff --git a/mobile/lib/shared/services/immich_logger.service.dart b/mobile/lib/shared/services/immich_logger.service.dart index ebcf828f3f..b66177e570 100644 --- a/mobile/lib/shared/services/immich_logger.service.dart +++ b/mobile/lib/shared/services/immich_logger.service.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/shared/models/logger_message.model.dart'; +import 'package:immich_mobile/shared/models/store.dart'; import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; @@ -18,7 +19,7 @@ import 'package:share_plus/share_plus.dart'; /// and generate a csv file. class ImmichLogger { static final ImmichLogger _instance = ImmichLogger._internal(); - final maxLogEntries = 200; + final maxLogEntries = 500; final Isar _db = Isar.getInstance()!; List _msgBuffer = []; Timer? _timer; @@ -27,10 +28,13 @@ class ImmichLogger { ImmichLogger._internal() { _removeOverflowMessages(); - Logger.root.level = Level.INFO; + final int levelId = Store.get(StoreKey.logLevel, 5); // 5 is INFO + Logger.root.level = Level.LEVELS[levelId]; Logger.root.onRecord.listen(_writeLogToDatabase); } + set level(Level level) => Logger.root.level = level; + List get messages { final inDb = _db.loggerMessages.where(sort: Sort.desc).anyId().findAllSync(); diff --git a/mobile/lib/shared/services/sync.service.dart b/mobile/lib/shared/services/sync.service.dart index a6da152b31..3447e817f3 100644 --- a/mobile/lib/shared/services/sync.service.dart +++ b/mobile/lib/shared/services/sync.service.dart @@ -389,7 +389,13 @@ class SyncService { _addAlbumFromDevice(ape, existing, excludedAssets), onlySecond: (Album a) => _removeAlbumFromDb(a, deleteCandidates), ); + _log.fine( + "Syncing all local albums almost done. Collected ${deleteCandidates.length} asset candidates to delete", + ); final pair = _handleAssetRemoval(deleteCandidates, existing, remote: false); + _log.fine( + "${pair.first.length} assets to delete, ${pair.second.length} to update", + ); if (pair.first.isNotEmpty || pair.second.isNotEmpty) { await _db.writeTxn(() async { await _db.assets.deleteAll(pair.first); @@ -415,6 +421,7 @@ class SyncService { bool forceRefresh = false, ]) async { if (!forceRefresh && !await _hasAssetPathEntityChanged(ape, album)) { + _log.fine("Local album ${ape.name} has not changed. Skipping sync."); return false; } if (!forceRefresh && @@ -441,9 +448,18 @@ class SyncService { album.name == ape.name && album.modifiedAt == ape.lastModified) { // changes only affeted excluded albums + _log.fine( + "Only excluded assets in local album ${ape.name} changed. Stopping sync.", + ); return false; } + _log.fine( + "Syncing local album ${ape.name}. ${toAdd.length} assets to add, ${toUpdate.length} to update, ${toDelete.length} to delete", + ); final result = await _linkWithExistingFromDb(toAdd); + _log.fine( + "Linking assets to add with existing from db. ${result.first.length} existing, ${result.second.length} to update", + ); deleteCandidates.addAll(toDelete); existing.addAll(result.first); album.name = ape.name; @@ -462,9 +478,9 @@ class SyncService { album.thumbnail.value ??= await album.assets.filter().findFirst(); await album.thumbnail.save(); }); - _log.info("Synced changes of local album $ape to DB"); + _log.info("Synced changes of local album ${ape.name} to DB"); } on IsarError catch (e) { - _log.severe("Failed to update synced album $ape in DB: $e"); + _log.severe("Failed to update synced album ${ape.name} in DB: $e"); } return true; @@ -499,9 +515,9 @@ class SyncService { await album.assets.update(link: result.first + result.second); await _db.albums.put(album); }); - _log.info("Fast synced local album $ape to DB"); + _log.info("Fast synced local album ${ape.name} to DB"); } on IsarError catch (e) { - _log.severe("Failed to fast sync local album $ape to DB: $e"); + _log.severe("Failed to fast sync local album ${ape.name} to DB: $e"); return false; } @@ -515,7 +531,7 @@ class SyncService { List existing, [ Set? excludedAssets, ]) async { - _log.info("Syncing a new local album to DB: $ape"); + _log.info("Syncing a new local album to DB: ${ape.name}"); final Album a = Album.local(ape); final result = await _linkWithExistingFromDb( await ape.getAssets(excludedAssets: excludedAssets), @@ -531,9 +547,9 @@ class SyncService { a.thumbnail.value = thumb; try { await _db.writeTxn(() => _db.albums.store(a)); - _log.info("Added a new local album to DB: $ape"); + _log.info("Added a new local album to DB: ${ape.name}"); } on IsarError catch (e) { - _log.severe("Failed to add new local album $ape to DB: $e"); + _log.severe("Failed to add new local album ${ape.name} to DB: $e"); } } @@ -574,7 +590,11 @@ class SyncService { return true; } }, - onlyFirst: (Asset a) => {}, + onlyFirst: (Asset a) => _log.finer( + "_linkWithExistingFromDb encountered asset only in DB: $a", + null, + StackTrace.current, + ), onlySecond: (Asset b) => toUpsert.add(b), ); return Pair(existing, toUpsert); @@ -663,6 +683,7 @@ Pair, List> _handleAssetRemoval( compare: Asset.compareById, remote: remote, ); + assert(triple.first.isEmpty, "toAdd should be empty in _handleAssetRemoval"); return Pair(triple.third.map((e) => e.id).toList(), triple.second); } diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index 77ce350ec5..a74c8427a8 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -112,7 +112,9 @@ FutureOr _migrateDuplicatedAssetsBox(Box box) { Future _migrateAppSettingsBox(Box box) async { for (AppSettingsEnum s in AppSettingsEnum.values) { - await _migrateKey(box, s.hiveKey, s.storeKey); + if (s.hiveKey != null) { + await _migrateKey(box, s.hiveKey!, s.storeKey); + } } }