From 3cd7f5ab904d2aa294af44b642456c73ee33b479 Mon Sep 17 00:00:00 2001 From: Brandon Wees Date: Wed, 6 Aug 2025 10:49:29 -0500 Subject: [PATCH] feat: use sqlite for logging (#20414) * feat: use drift for logging * fix: tests * feat: use the truncate limit from constants.ts as default * chore: move setupAll to top level and restructure * chore: code review changes * fix: inherits * feat: raise log line limit to 2000 * limit getAll to 250 lines * delete DLog and make LogRepository not a singleton * fix: drift build settings and `make migration` * fix: tests * remove sensitive log --------- Co-authored-by: Alex --- mobile/build.yaml | 1 + mobile/lib/constants/constants.dart | 2 +- mobile/lib/domain/services/hash.service.dart | 3 - .../domain/services/local_sync.service.dart | 10 +-- mobile/lib/domain/services/log.service.dart | 18 ++--- .../domain/services/sync_stream.service.dart | 2 - .../infrastructure/entities/log.entity.dart | 68 ++++++----------- .../entities/log.entity.drift.dart | Bin 0 -> 22772 bytes .../infrastructure/entities/log.entity.g.dart | Bin 37371 -> 0 bytes .../repositories/log.repository.dart | 71 ++++++++++++------ .../repositories/logger_db.repository.dart | 27 +++++++ .../logger_db.repository.drift.dart | Bin 0 -> 1032 bytes .../repositories/sync_api.repository.dart | 2 - .../lib/pages/common/app_log_detail.page.dart | 6 +- .../lib/pages/common/splash_screen.page.dart | 1 - .../presentation/pages/dev/dev_logger.dart | 68 ----------------- .../pages/dev/feat_in_development.page.dart | 62 +-------------- mobile/lib/utils/bootstrap.dart | 9 ++- mobile/lib/utils/isolate.dart | 2 +- .../domain/services/log_service_test.dart | 2 +- .../test/infrastructure/repository.mock.dart | 2 +- .../modules/shared/sync_service_test.dart | 38 +++++++--- mobile/test/test_utils.dart | 2 - 23 files changed, 155 insertions(+), 241 deletions(-) create mode 100644 mobile/lib/infrastructure/entities/log.entity.drift.dart delete mode 100644 mobile/lib/infrastructure/entities/log.entity.g.dart create mode 100644 mobile/lib/infrastructure/repositories/logger_db.repository.dart create mode 100644 mobile/lib/infrastructure/repositories/logger_db.repository.drift.dart delete mode 100644 mobile/lib/presentation/pages/dev/dev_logger.dart diff --git a/mobile/build.yaml b/mobile/build.yaml index 76cc0a998..cb718d9d3 100644 --- a/mobile/build.yaml +++ b/mobile/build.yaml @@ -19,6 +19,7 @@ targets: - lib/infrastructure/entities/*.dart - lib/infrastructure/entities/*.drift - lib/infrastructure/repositories/db.repository.dart + - lib/infrastructure/repositories/logger_db.repository.dart drift_dev:modular: enabled: true options: *drift_options diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart index d984b468d..616a306d9 100644 --- a/mobile/lib/constants/constants.dart +++ b/mobile/lib/constants/constants.dart @@ -3,7 +3,7 @@ const double downloadCompleted = -1; const double downloadFailed = -2; // Number of log entries to retain on app start -const int kLogTruncateLimit = 250; +const int kLogTruncateLimit = 2000; // Sync const int kSyncEventBatchSize = 5000; diff --git a/mobile/lib/domain/services/hash.service.dart b/mobile/lib/domain/services/hash.service.dart index 36a4b9efb..a8eea2c25 100644 --- a/mobile/lib/domain/services/hash.service.dart +++ b/mobile/lib/domain/services/hash.service.dart @@ -6,7 +6,6 @@ import 'package:immich_mobile/infrastructure/repositories/local_album.repository import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart'; import 'package:logging/logging.dart'; class HashService { @@ -46,7 +45,6 @@ class HashService { stopwatch.stop(); _log.info("Hashing took - ${stopwatch.elapsedMilliseconds}ms"); - DLog.log("Hashing took - ${stopwatch.elapsedMilliseconds}ms"); } /// Processes a list of [LocalAsset]s, storing their hash and updating the assets in the DB @@ -101,7 +99,6 @@ class HashService { } _log.fine("Hashed ${hashed.length}/${toHash.length} assets"); - DLog.log("Hashed ${hashed.length}/${toHash.length} assets"); await _localAssetRepository.updateHashes(hashed); await _storageRepository.clearCache(); diff --git a/mobile/lib/domain/services/local_sync.service.dart b/mobile/lib/domain/services/local_sync.service.dart index 13ebecfd4..119954cb4 100644 --- a/mobile/lib/domain/services/local_sync.service.dart +++ b/mobile/lib/domain/services/local_sync.service.dart @@ -6,7 +6,6 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:logging/logging.dart'; import 'package:platform/platform.dart'; @@ -30,19 +29,17 @@ class LocalSyncService { try { if (full || await _nativeSyncApi.shouldFullSync()) { _log.fine("Full sync request from ${full ? "user" : "native"}"); - DLog.log("Full sync request from ${full ? "user" : "native"}"); return await fullSync(); } final delta = await _nativeSyncApi.getMediaChanges(); if (!delta.hasChanges) { _log.fine("No media changes detected. Skipping sync"); - DLog.log("No media changes detected. Skipping sync"); return; } - DLog.log("Delta updated: ${delta.updates.length}"); - DLog.log("Delta deleted: ${delta.deletes.length}"); + _log.fine("Delta updated: ${delta.updates.length}"); + _log.fine("Delta deleted: ${delta.deletes.length}"); final deviceAlbums = await _nativeSyncApi.getAlbums(); await _localAlbumRepository.updateAll(deviceAlbums.toLocalAlbums()); @@ -83,7 +80,6 @@ class LocalSyncService { } finally { stopwatch.stop(); _log.info("Device sync took - ${stopwatch.elapsedMilliseconds}ms"); - DLog.log("Device sync took - ${stopwatch.elapsedMilliseconds}ms"); } } @@ -106,7 +102,6 @@ class LocalSyncService { await _nativeSyncApi.checkpointSync(); stopwatch.stop(); _log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms"); - DLog.log("Full device sync took - ${stopwatch.elapsedMilliseconds}ms"); } catch (e, s) { _log.severe("Error performing full device sync", e, s); } @@ -150,7 +145,6 @@ class LocalSyncService { // Faster path - only new assets added if (await checkAddition(dbAlbum, deviceAlbum)) { _log.fine("Fast synced device album ${dbAlbum.name}"); - DLog.log("Fast synced device album ${dbAlbum.name}"); return true; } diff --git a/mobile/lib/domain/services/log.service.dart b/mobile/lib/domain/services/log.service.dart index ff72ec550..98cb24d9c 100644 --- a/mobile/lib/domain/services/log.service.dart +++ b/mobile/lib/domain/services/log.service.dart @@ -14,7 +14,7 @@ import 'package:logging/logging.dart'; /// writes them to a persistent [ILogRepository], and manages log levels /// via [IStoreRepository] class LogService { - final IsarLogRepository _logRepository; + final LogRepository _logRepository; final IsarStoreRepository _storeRepository; final List _msgBuffer = []; @@ -37,7 +37,7 @@ class LogService { } static Future init({ - required IsarLogRepository logRepository, + required LogRepository logRepository, required IsarStoreRepository storeRepository, bool shouldBuffer = true, }) async { @@ -50,7 +50,7 @@ class LogService { } static Future create({ - required IsarLogRepository logRepository, + required LogRepository logRepository, required IsarStoreRepository storeRepository, bool shouldBuffer = true, }) async { @@ -85,7 +85,7 @@ class LogService { if (_shouldBuffer) { _msgBuffer.add(record); - _flushTimer ??= Timer(const Duration(seconds: 5), () => unawaited(flushBuffer())); + _flushTimer ??= Timer(const Duration(seconds: 5), () => unawaited(_flushBuffer())); } else { unawaited(_logRepository.insert(record)); } @@ -108,20 +108,18 @@ class LogService { await _logRepository.deleteAll(); } - void flush() { + Future flush() { _flushTimer?.cancel(); - // TODO: Rename enable this after moving to sqlite - #16504 - // await _flushBufferToDatabase(); + return _flushBuffer(); } Future dispose() { _flushTimer?.cancel(); _logSubscription.cancel(); - return flushBuffer(); + return _flushBuffer(); } - // TOOD: Move this to private once Isar is removed - Future flushBuffer() async { + Future _flushBuffer() async { _flushTimer = null; final buffer = [..._msgBuffer]; _msgBuffer.clear(); diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 7c25fbb34..5625635e4 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; -import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; @@ -26,7 +25,6 @@ class SyncStreamService { Future sync() { _logger.info("Remote sync request for user"); - DLog.log("Remote sync request for user"); // Start the sync stream and handle events return _syncApiRepository.streamChanges(_handleEvents); } diff --git a/mobile/lib/infrastructure/entities/log.entity.dart b/mobile/lib/infrastructure/entities/log.entity.dart index 6a38924e2..e57845982 100644 --- a/mobile/lib/infrastructure/entities/log.entity.dart +++ b/mobile/lib/infrastructure/entities/log.entity.dart @@ -1,47 +1,29 @@ -import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:isar/isar.dart'; +import 'package:drift/drift.dart'; +import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart'; +import 'package:immich_mobile/domain/models/log.model.dart' as domain; -part 'log.entity.g.dart'; +class LogMessageEntity extends Table { + const LogMessageEntity(); -@Collection(inheritance: false) -class LoggerMessage { - final Id id = Isar.autoIncrement; - final String message; - final String? details; - @Enumerated(EnumType.ordinal) - final LogLevel level; - final DateTime createdAt; - final String? context1; - final String? context2; + @override + String get tableName => 'logger_messages'; - const LoggerMessage({ - required this.message, - required this.details, - this.level = LogLevel.info, - required this.createdAt, - required this.context1, - required this.context2, - }); - - LogMessage toDto() { - return LogMessage( - message: message, - level: level, - createdAt: createdAt, - logger: context1, - error: details, - stack: context2, - ); - } - - static LoggerMessage fromDto(LogMessage log) { - return LoggerMessage( - message: log.message, - details: log.error, - level: log.level, - createdAt: log.createdAt, - context1: log.logger, - context2: log.stack, - ); - } + IntColumn get id => integer().autoIncrement()(); + TextColumn get message => text()(); + TextColumn get details => text().nullable()(); + IntColumn get level => intEnum()(); + DateTimeColumn get createdAt => dateTime()(); + TextColumn get logger => text().nullable()(); + TextColumn get stack => text().nullable()(); +} + +extension LogMessageEntityDataDomainEx on LogMessageEntityData { + domain.LogMessage toDto() => domain.LogMessage( + message: message, + level: level, + createdAt: createdAt, + logger: logger, + error: details, + stack: stack, + ); } diff --git a/mobile/lib/infrastructure/entities/log.entity.drift.dart b/mobile/lib/infrastructure/entities/log.entity.drift.dart new file mode 100644 index 0000000000000000000000000000000000000000..d04cd5b7a2ab549e3e626896d5655d6be6c2f42f GIT binary patch literal 22772 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 TPGtW-t9Q;y9OGI2#|Qrdv9-;{ literal 0 HcmV?d00001 diff --git a/mobile/lib/infrastructure/entities/log.entity.g.dart b/mobile/lib/infrastructure/entities/log.entity.g.dart deleted file mode 100644 index 02fa817a08aa0e75101776b9b19d5d07ca0c6234..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37371 zcmeHQ|8Lv2693(Q1t0JLJDck!PSa*?lcH&|z5r>`E!iuEp%7GyPSnbhE6K_Hiu~{I z?np|cti*9F*^<`+TPuFMyU!hu$0IF0eahY)ogV%E=Hlp(y*)cTVo%uN89P0@VDHZk zkAMA>9sJ4u^XBxh(HPlb%z^=HdckFf`>`8OIu4XIUp5*~pR%6{`-7t$M|Su&@I2m+ z-N1jxeID9z5Ee`bhwBH|F!m+yxtD$r@+Sk=<5(^ptw9i4SkzmVirmxq>b4|&vvk?*W>{cGEE9cvtMD;QX#Fo;1??pQ-U{LI5P8~Z-* z^9WFwSbaN++iVo_0gO$9ivNe>K0pG5JlE%uETk-`d@@`d2pAh5Xyt4Y?JE zwhQepq3dvHLxt z1PYl7f9!d{4DWx1iZ6ldTOp6fp>N%|@s(vq5uiDQF%t;~u5WuFP2cvQhkYFYPG2G` z4930#2)TZo6pw<)MINF3=OFMnNDd4F`Y}h@ZSTgOL{>E7eRnXi;>n0djRyZa=6(dd z*gGDd1eXBXdqnz@rv}HM7EO?SZ?KFlzB0P(vr8T`m=WnH5!>5m@s%5O`iTLzHeNPv z8x2w(GX^*ZXofc1V<~Y_)B?(ANIe!djULT5)VNNMJ=uD``Fv}0b9d)?cXRi}&i0Gl z7gz_@G~!|G@~B7rGZ+CV#~}Dtv&TM&>Pbq5!5Ij94 z$_?>XLaKW&sVy~PhE$iNLmnPPD&#O9oj38lw7OHYl5VOdL(G9uFWey))rn*dk724r z)5H+GZSV(DU8-F>Ux;Kpk6&|ddNip79Q2uDPz^1*sL~AEpC_?onKAB*YdIW)+K2#yP9!ZcIJ%}y$!YImQAx9tXq!lT$70-opY|G;!$UTah3NUK9e zA|ATn7pEJZs|sx8@H^Wbrd64iX!Tk0T5GJv9|;TYgDWd&z*?BEbkdm=`eYCz@K#*SmVf0Z5L4V7X&ELrHmdaSwK z+3M^9r{G{3;2JYTb%4>K7`DKI&tG8G!SPPqD1HUq_hEq_L>xRU6oPfU!fD=4tM|dD zdS|N12Dz7OaFb|x(OdA)F?cdLM8|`5>m`hXK9n(X>kO8?F~>GU8a$+MOV|v+AF!5a z@UK0F>xLkJRT_bx_t-Z3S&Y>2xPKX6v7gws7~&QN!vAK|>@Jh0^azEMoQ6dLE9Ykh zAF=XkVA86DD{RJYlKFO@(cl94F?3Cj*26ic+EE&a9mshzbMuy`g3 z=XTke>e8L+awr{rLZ_>BQme#NuMBV7X&w9aK}TTvj5^++TXD`$13 zl@*s*@SlHa!lDgiM1A&Y;}d)RIvEXcJHeO$EMgF#P8gHA&fzctY)9}$1|(;=&;;4X zhs@O0Yyua(S0DDtH1aG0*OQTq7RB0yC1OeyIzacH0W!8TO?~^G&JSY$S$2R$Y|6AJG$2~^Q>)rYj18g; z6UUt_BU6fyCTD=)@PR${V%0ak3WFPRFh^k+gstWu{#QS^@e@O0xbFi$#QvSyLz8Ux zHr*oSK%fV$;9|am98CyDAX24mOA}lWgqrY3TMK#&;faEOdV+^-v|8K?4ctq3r0BYd za}6GLsP4uNeb~do^K*KRU^<2v4%5!68C?Zqq_IntFcaY4zqA{-FXh%Qhi}~-NWM3F z=pltpKm;6XODhtRU@5r1q#>4)pWeES6@o=xGePj>}5BAiyq_YL!8-P6@}H{@BhhU@|5YZ#SwY{#KN-+DGAuUcSiQ4E2FR@MP(tqq#d5R0cXJI%Wo5)vEK z{n6kEUKW8!jo>;*-^R9g5s08AQM>}9Na#$^KJ)m7bHC*UH$3cNoW{l@NaNr?vtR;H zy7FcjP-+ROn5RKptfaVi6Bcr#jGgW6F*N#)9rYD}X#cuBkw~l;jVUqOj2A5cs1r49s)^FG3rj&YF}01J!5 zvCPEkrbm8Bt6iq~sr4nXPRL~nmt}~Db_N#%F|5zq%g8HAHL9I7;+<8~6^L89g#w$c_S>Knz5wjpK;&7)AM%3?&hfAoe2W z%2an;L0xUCAYqUm`rL&zFh95B&CuO90SMw1V;~t=B@8OC61)G`B2x|Cf?NS+EY9)# zF?O#p5ssdsFh7vdO=>ckiVhqKr%|q$&$)=D(-qDfFm>OP($EAH9Sw-uFUbU_ z;()9Yh;iL1>+t}bGhn1(h){;V&Zy&VJ?TM%2D$XFiEB+^PELO(h6B(W)IyJDm{M;g zCNa%FN`poer4&f9hA>7|MB&5Yu~g-zZce~8)5Yo$nLcIp2rQZO6rJg<2+5vE8|dW^ ziN+wR&=;mvyYcfFE|x_fAyeEEc2dPf6z+W~CbgM8Q^~dPzem1Pb@;hraqYl|Jqlbj z@}mmdKJIyl#kB+fy$y$zJi&ehS8;d0M_`q|_lS?EXlvhVW8l(knFte|^#`H3WZs(kmwTwMnnQHgZKdg?rK~fTh|1Ei1huw$m$uVn%vp zsc_KEIrt5&+E=SzJP3tUBMGN?OwEM0%Vj6x6j^=SYr>=Q!t#twT_#xmjSk(Sw3zt8 zHwBca$?vc4&Pgl_^<!mDOzG;tV_Pi&a0>5S23-=ToBaauWseg@~Rd$<8-j9cNP_1`puV0=hP*`dTc=pcUA>d z_)od9SXV73vC0~#aFMvCuKF`#A2kED&_@*&6#3Ncwql-IR~HslUVtn{?~9wIUaB&qaqnm$*{hJkeAJ9;NCd3a;fo4x)dPZ`=+fK z54~AaS0yGZQ~fo#$Rbu(^%)DEoPXMKCrb)i>?${BXY6tEl)Adruv*T0O^&njRGc5? zR}20%IL@jo^lNaORafZO;5aJ}`syENNv`PNYw^S7*GL)kN_2Y>W zbfAVVSqX|vNm@|DD=A7*!=d;{W${W#FIE&k59BPCyq5!ihW}E)Kf$ke6%W14-Iw5i zB=E#{Ij|eNmje2G@y*iWVNYYP(kx!`UJmpb{&T>e5yzp}7gyYWe1s)>FBoOP_ojh` z`bJqM|K;eqFJsV>))qsA8b(`V07Y@1O^M!Zty**p)|M=~icbm);1s|@rD}NbP380ptaHXZ zYEWHkD(G36($e5!xjHL$9aPYgzC?PY_!*{Fk(KEx_11IfYDPZdgZvmh`O7&Eu_)kI z1I%{d@TGhB_A+%kgU|5Vu?t^1-iKe`s38iQzG}czd&or~I-fO&t6Z=1xK&93MWQU8 z6`eVK&TP)ROX3_}UjLGe>Vd~<>P=X=peqktI`4|moXp deleteAll() async { - await transaction(() async => await _db.loggerMessages.clear()); + await _db.logMessageEntity.deleteAll(); return true; } - Future> getAll() async { - final logs = await _db.loggerMessages.where().sortByCreatedAtDesc().findAll(); - return logs.map((l) => l.toDto()).toList(); + Future> getAll({int limit = 250}) async { + final query = _db.logMessageEntity.select() + ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) + ..limit(limit); + + return query.map((log) => log.toDto()).get(); + } + + LogMessageEntityCompanion _toEntityCompanion(LogMessage log) { + return LogMessageEntityCompanion.insert( + message: log.message, + level: log.level, + createdAt: log.createdAt, + logger: Value(log.logger), + details: Value(log.error), + stack: Value(log.stack), + ); } Future insert(LogMessage log) async { - final logEntity = LoggerMessage.fromDto(log); + final logEntity = _toEntityCompanion(log); try { - await transaction(() => _db.loggerMessages.put(logEntity)); + await _db.logMessageEntity.insertOne(logEntity); } catch (e) { return false; } @@ -30,19 +46,30 @@ class IsarLogRepository extends IsarDatabaseRepository { } Future insertAll(Iterable logs) async { - await transaction(() async { - final logEntities = logs.map((log) => LoggerMessage.fromDto(log)).toList(); - await _db.loggerMessages.putAll(logEntities); - }); + final logEntities = logs.map(_toEntityCompanion).toList(); + await _db.logMessageEntity.insertAll(logEntities); + return true; } - Future truncate({int limit = 250}) async { - await transaction(() async { - final count = await _db.loggerMessages.count(); - if (count <= limit) return; - final toRemove = count - limit; - await _db.loggerMessages.where().limit(toRemove).deleteAll(); - }); + Future deleteByLogger(String logger) async { + await _db.logMessageEntity.deleteWhere((row) => row.logger.equals(logger)); + } + + Stream> watchMessages(String logger) { + final query = _db.logMessageEntity.select() + ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) + ..where((row) => row.logger.equals(logger)); + + return query.watch().map((rows) => rows.map((row) => row.toDto()).toList()); + } + + Future truncate({int limit = kLogTruncateLimit}) async { + final totalCount = await _db.managers.logMessageEntity.count(); + if (totalCount > limit) { + final rowsToDelete = totalCount - limit; + + await _db.managers.logMessageEntity.orderBy((o) => o.createdAt.asc()).limit(rowsToDelete).delete(); + } } } diff --git a/mobile/lib/infrastructure/repositories/logger_db.repository.dart b/mobile/lib/infrastructure/repositories/logger_db.repository.dart new file mode 100644 index 000000000..583fc4281 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/logger_db.repository.dart @@ -0,0 +1,27 @@ +import 'package:drift/drift.dart'; +import 'package:drift_flutter/drift_flutter.dart'; +import 'package:immich_mobile/domain/interfaces/db.interface.dart'; +import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; + +import 'logger_db.repository.drift.dart'; + +@DriftDatabase(tables: [LogMessageEntity]) +class DriftLogger extends $DriftLogger implements IDatabaseRepository { + DriftLogger([QueryExecutor? executor]) + : super( + executor ?? driftDatabase(name: 'immich_logs', native: const DriftNativeOptions(shareAcrossIsolates: true)), + ); + + @override + int get schemaVersion => 1; + + @override + MigrationStrategy get migration => MigrationStrategy( + beforeOpen: (details) async { + await customStatement('PRAGMA foreign_keys = ON'); + await customStatement('PRAGMA synchronous = NORMAL'); + await customStatement('PRAGMA journal_mode = WAL'); + await customStatement('PRAGMA busy_timeout = 500'); + }, + ); +} diff --git a/mobile/lib/infrastructure/repositories/logger_db.repository.drift.dart b/mobile/lib/infrastructure/repositories/logger_db.repository.drift.dart new file mode 100644 index 0000000000000000000000000000000000000000..8389d3a827f5bbe4a47de61ddb121c6d5a183cfb GIT binary patch literal 1032 zcma))(M#Mw5XRs0SImQ81WEKw?s}(!QUpax@2!+$vY8}nlijd8sfN=3y|e4x8BY(I zhmh>f{C2+mzC4E#9YbYZhYZiU2$?;d}jf74Cq0h%|8_CuG6YnceW5>u1)38vl|o-j&d@rS(DFOTW9fS%qGkP z~C^Tmn02$ERu!zMU+MKovG~Z=M)}`CsKU5 zTt*Stwy`Lv*qkOy-&I2$#_`0E?71OFE3tovImAq(;ORK`+H+{zIxs _parseLines(List lines) { diff --git a/mobile/lib/pages/common/app_log_detail.page.dart b/mobile/lib/pages/common/app_log_detail.page.dart index a9cf634fa..c9773f36e 100644 --- a/mobile/lib/pages/common/app_log_detail.page.dart +++ b/mobile/lib/pages/common/app_log_detail.page.dart @@ -65,7 +65,7 @@ class AppLogDetailPage extends HookConsumerWidget { ); } - buildLogContext1(String context1) { + buildLogContext(String logger) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( @@ -86,7 +86,7 @@ class AppLogDetailPage extends HookConsumerWidget { child: Padding( padding: const EdgeInsets.all(8.0), child: SelectableText( - context1.toString(), + logger.toString(), style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "Inconsolata"), ), ), @@ -103,7 +103,7 @@ class AppLogDetailPage extends HookConsumerWidget { children: [ buildTextWithCopyButton("MESSAGE", logMessage.message), if (logMessage.error != null) buildTextWithCopyButton("DETAILS", logMessage.error.toString()), - if (logMessage.logger != null) buildLogContext1(logMessage.logger.toString()), + if (logMessage.logger != null) buildLogContext(logMessage.logger.toString()), if (logMessage.stack != null) buildTextWithCopyButton("STACK TRACE", logMessage.stack.toString()), ], ), diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index 159a5b4fe..87ea7849c 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -49,7 +49,6 @@ class SplashScreenPageState extends ConsumerState { final wsProvider = ref.read(websocketProvider.notifier); ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( (a) { - log.info('Successfully updated auth info with access token: $accessToken'); try { wsProvider.connect(); infoProvider.getServerInfo(); diff --git a/mobile/lib/presentation/pages/dev/dev_logger.dart b/mobile/lib/presentation/pages/dev/dev_logger.dart deleted file mode 100644 index ab9849f87..000000000 --- a/mobile/lib/presentation/pages/dev/dev_logger.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; -import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; -// ignore: import_rule_isar -import 'package:isar/isar.dart'; - -const kDevLoggerTag = 'DEV'; - -abstract final class DLog { - const DLog(); - - static Stream> watchLog() { - final db = Isar.getInstance(); - if (db == null) { - return const Stream.empty(); - } - - return db.loggerMessages - .filter() - .context1EqualTo(kDevLoggerTag) - .sortByCreatedAtDesc() - .watch(fireImmediately: true) - .map((logs) => logs.map((log) => log.toDto()).toList()); - } - - static void clearLog() { - final db = Isar.getInstance(); - if (db == null) { - return; - } - - db.writeTxnSync(() { - db.loggerMessages.filter().context1EqualTo(kDevLoggerTag).deleteAllSync(); - }); - } - - static void log(String message, [Object? error, StackTrace? stackTrace]) { - if (!Platform.environment.containsKey('FLUTTER_TEST')) { - debugPrint('[$kDevLoggerTag] [${DateTime.now()}] $message'); - } - if (error != null) { - debugPrint('Error: $error'); - } - if (stackTrace != null) { - debugPrint('StackTrace: $stackTrace'); - } - - final isar = Isar.getInstance(); - if (isar == null) { - return; - } - - final record = LogMessage( - message: message, - level: LogLevel.info, - createdAt: DateTime.now(), - logger: kDevLoggerTag, - error: error?.toString(), - stack: stackTrace?.toString(), - ); - - unawaited(IsarLogRepository(isar).insert(record)); - } -} diff --git a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart index 2ab1eeaaa..d3f0e3c1b 100644 --- a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart +++ b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart @@ -2,19 +2,16 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; import 'package:drift/drift.dart' hide Column; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:logging/logging.dart'; final _features = [ _Feature( @@ -37,7 +34,7 @@ final _features = [ DriftAssetSelectionTimelineRoute(lockedSelectionAssets: assets.toSet()), ); - DLog.log("Selected ${selectedAssets?.length ?? 0} assets"); + Logger("FeaturesInDevelopment").fine("Selected ${selectedAssets?.length ?? 0} assets"); return Future.value(); }, @@ -159,7 +156,6 @@ class FeatInDevPage extends StatelessWidget { ), ), const Divider(height: 0), - const Flexible(child: _DevLogs()), ], ), ); @@ -174,57 +170,3 @@ class _Feature { final TextStyle? style; final Future Function(BuildContext, WidgetRef _) onTap; } - -class _DevLogs extends StatelessWidget { - const _DevLogs(); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - actions: [ - IconButton( - onPressed: DLog.clearLog, - icon: Icon( - Icons.delete_outline_rounded, - size: 20.0, - color: context.primaryColor, - semanticLabel: "Clear logs", - ), - ), - ], - centerTitle: true, - ), - body: StreamBuilder( - initialData: [], - stream: DLog.watchLog(), - builder: (_, logMessages) { - return ListView.separated( - itemBuilder: (ctx, index) { - final logMessage = logMessages.data![index]; - return ListTile( - title: Text( - logMessage.message, - style: TextStyle(color: ctx.colorScheme.onSurface, fontSize: 14.0, overflow: TextOverflow.ellipsis), - ), - subtitle: Text( - "at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)}", - style: TextStyle(color: ctx.colorScheme.onSurfaceSecondary, fontSize: 12.0), - ), - dense: true, - visualDensity: VisualDensity.compact, - tileColor: Colors.transparent, - minLeadingWidth: 10, - ); - }, - separatorBuilder: (_, index) { - return const Divider(height: 0); - }, - itemCount: logMessages.data?.length ?? 0, - ); - }, - ), - ); - } -} diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index 8c4ca077c..9cab9caf9 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -12,10 +12,10 @@ import 'package:immich_mobile/entities/etag.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:isar/isar.dart'; import 'package:path_provider/path_provider.dart'; @@ -36,7 +36,6 @@ abstract final class Bootstrap { UserSchema, BackupAlbumSchema, DuplicatedAssetSchema, - LoggerMessageSchema, ETagSchema, if (Platform.isAndroid) AndroidDeviceAssetSchema, if (Platform.isIOS) IOSDeviceAssetSchema, @@ -49,9 +48,13 @@ abstract final class Bootstrap { } static Future initDomain(Isar db, {bool shouldBufferLogs = true}) async { + // load drift dbs + final loggerDb = DriftLogger(); + await StoreService.init(storeRepository: IsarStoreRepository(db)); + await LogService.init( - logRepository: IsarLogRepository(db), + logRepository: LogRepository(loggerDb), storeRepository: IsarStoreRepository(db), shouldBuffer: shouldBufferLogs, ); diff --git a/mobile/lib/utils/isolate.dart b/mobile/lib/utils/isolate.dart index a57c3ebbd..01903cfc7 100644 --- a/mobile/lib/utils/isolate.dart +++ b/mobile/lib/utils/isolate.dart @@ -56,7 +56,7 @@ Cancelable runInIsolateGentle({ log.severe("Error in runInIsolateGentle ${debugLabel == null ? '' : ' for $debugLabel'}", error, stack); } finally { try { - await LogService.I.flushBuffer(); + await LogService.I.flush(); await ref.read(driftProvider).close(); // Close Isar safely diff --git a/mobile/test/domain/services/log_service_test.dart b/mobile/test/domain/services/log_service_test.dart index 87b32b829..b4feac4e2 100644 --- a/mobile/test/domain/services/log_service_test.dart +++ b/mobile/test/domain/services/log_service_test.dart @@ -28,7 +28,7 @@ final _kWarnLog = LogMessage( void main() { late LogService sut; - late IsarLogRepository mockLogRepo; + late LogRepository mockLogRepo; late IsarStoreRepository mockStoreRepo; setUp(() async { diff --git a/mobile/test/infrastructure/repository.mock.dart b/mobile/test/infrastructure/repository.mock.dart index ed20f177b..29ef9462a 100644 --- a/mobile/test/infrastructure/repository.mock.dart +++ b/mobile/test/infrastructure/repository.mock.dart @@ -12,7 +12,7 @@ import 'package:mocktail/mocktail.dart'; class MockStoreRepository extends Mock implements IsarStoreRepository {} -class MockLogRepository extends Mock implements IsarLogRepository {} +class MockLogRepository extends Mock implements LogRepository {} class MockIsarUserRepository extends Mock implements IsarUserRepository {} diff --git a/mobile/test/modules/shared/sync_service_test.dart b/mobile/test/modules/shared/sync_service_test.dart index 22fd3cacf..767a52b8d 100644 --- a/mobile/test/modules/shared/sync_service_test.dart +++ b/mobile/test/modules/shared/sync_service_test.dart @@ -1,3 +1,5 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/constants/enums.dart'; @@ -9,9 +11,10 @@ import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:immich_mobile/services/sync.service.dart'; import 'package:mocktail/mocktail.dart'; @@ -49,6 +52,28 @@ void main() { ); } + final owner = UserDto( + id: "1", + updatedAt: DateTime.now(), + email: "a@b.c", + name: "first last", + isAdmin: false, + profileChangedAt: DateTime.now(), + ); + + setUpAll(() async { + final loggerDb = DriftLogger(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); + final LogRepository logRepository = LogRepository(loggerDb); + + WidgetsFlutterBinding.ensureInitialized(); + final db = await TestUtils.initIsar(); + + db.writeTxnSync(() => db.clearSync()); + await StoreService.init(storeRepository: IsarStoreRepository(db)); + await Store.put(StoreKey.currentUser, owner); + await LogService.init(logRepository: logRepository, storeRepository: IsarStoreRepository(db)); + }); + group('Test SyncService grouped', () { final MockHashService hs = MockHashService(); final MockEntityService entityService = MockEntityService(); @@ -74,16 +99,9 @@ void main() { isAdmin: false, profileChangedAt: DateTime(2021), ); - late SyncService s; - setUpAll(() async { - WidgetsFlutterBinding.ensureInitialized(); - final db = await TestUtils.initIsar(); - db.writeTxnSync(() => db.clearSync()); - await StoreService.init(storeRepository: IsarStoreRepository(db)); - await Store.put(StoreKey.currentUser, owner); - await LogService.init(logRepository: IsarLogRepository(db), storeRepository: IsarStoreRepository(db)); - }); + late SyncService s; + final List initialAssets = [ makeAsset(checksum: "a", remoteId: "0-1"), makeAsset(checksum: "b", remoteId: "2-1"), diff --git a/mobile/test/test_utils.dart b/mobile/test/test_utils.dart index d932e2ffc..9b59773d3 100644 --- a/mobile/test/test_utils.dart +++ b/mobile/test/test_utils.dart @@ -14,7 +14,6 @@ import 'package:immich_mobile/entities/etag.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:isar/isar.dart'; @@ -48,7 +47,6 @@ abstract final class TestUtils { UserSchema, BackupAlbumSchema, DuplicatedAssetSchema, - LoggerMessageSchema, ETagSchema, AndroidDeviceAssetSchema, IOSDeviceAssetSchema,