mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feature(mobile): configurable log level (#2248)
* feature(mobile): configurable log level * increase maxLogEntries to 500 --------- Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							4952b3a2d6
						
					
				
				
					commit
					d500ef77cf
				
			| @@ -43,17 +43,14 @@ enum AppSettingsEnum<T> { | ||||
|     "selectedAlbumSortOrder", | ||||
|     0, | ||||
|   ), | ||||
|   advancedTroubleshooting<bool>( | ||||
|     StoreKey.advancedTroubleshooting, | ||||
|     "advancedTroubleshooting", | ||||
|     false, | ||||
|   ), | ||||
|   advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false), | ||||
|   logLevel<int>(StoreKey.logLevel, null, 5) // Level.INFO = 5 | ||||
|   ; | ||||
|  | ||||
|   const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue); | ||||
|  | ||||
|   final StoreKey<T> storeKey; | ||||
|   final String hiveKey; | ||||
|   final String? hiveKey; | ||||
|   final T defaultValue; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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<bool>( | ||||
|           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, | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -168,6 +168,7 @@ enum StoreKey<T> { | ||||
|   albumThumbnailCacheSize<int>(112, type: int), | ||||
|   selectedAlbumSortOrder<int>(113, type: int), | ||||
|   advancedTroubleshooting<bool>(114, type: bool), | ||||
|   logLevel<int>(115, type: int), | ||||
|   ; | ||||
|  | ||||
|   const StoreKey( | ||||
|   | ||||
| @@ -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<LoggerMessage> _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<LoggerMessage> get messages { | ||||
|     final inDb = | ||||
|         _db.loggerMessages.where(sort: Sort.desc).anyId().findAllSync(); | ||||
|   | ||||
| @@ -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<Asset> existing, [ | ||||
|     Set<String>? 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<int>, List<Asset>> _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); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -112,7 +112,9 @@ FutureOr<void> _migrateDuplicatedAssetsBox(Box<HiveDuplicatedAssets> box) { | ||||
|  | ||||
| Future<void> _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); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user