Skip to content

Commit 91267fc

Browse files
authored
Feat: Logger service background integration (#406)
* feat: add logger service with memory buffer and Riverpod provider * fix : update config with logger constants * feat : connect logs screen to logger service with real-time display * feat: sync logging toggle with MemoryLogOutput * refactor: migrate files to use logger singleton * docs: updating docs * Migrate NostrService to use logger singleton * Migrate mostro_storage to use logger singleton * Update docs for Phase 3 completion * Enable isolate log receiver for background services * Update docs for Phase 4 completion * Add bottom padding to logs list to prevent overlap with system buttons * Localize relative time strings using timeAgoWithLocale extension * Increase bottom padding in logs list to prevent overlap with system navigation * Fix typo in LOGGING_IMPLEMENTATION.md * Configure logger with IsolateLogOutput in mobile background isolate * Print background isolate logs to console only in debug mode * Add fallback logging for errors before logger initialization
1 parent 0e2cb23 commit 91267fc

13 files changed

+144
-111
lines changed

docs/LOGGING_IMPLEMENTATION.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@ Implementation of a comprehensive logging system for MostroP2P mobile app with i
2222
- Connect UI to actual logger
2323
- Test with 2 files: RelaysNotifier, SubscriptionManager
2424

25-
### Phase 3: Core Services Migration (Current)
25+
### Phase 3: Core Services Migration (Completed)
2626
- NostrService
2727
- MostroService
2828
- DeepLinkService
29-
- 2 additional core files
29+
- mostro_storage
30+
- AbstractMostroNotifier + subclasses (AddOrderNotifier, OrderNotifier)
3031

31-
### Phase 4: Background Services
32-
- Mobile and desktop background service
33-
- Isolate logging
32+
### Phase 4: Background Services (Completed)
33+
- Mobile background service with isolate logging
34+
- Desktop background service with isolate logging
35+
- Isolate log receiver initialized in main.dart
3436

3537
### Phase 5: File Export & Persistence
3638
- Auto-save to storage
@@ -204,6 +206,6 @@ void backgroundMain(SendPort sendPort) async {
204206

205207
---
206208

207-
**Version**: 3
208-
**Status**: Phase 2 - Ready
209-
**Last Updated**: 2026-01-06
209+
**Version**: 5
210+
**Status**: Phase 4 - Completed
211+
**Last Updated**: 2026-01-12

lib/background/background.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
import 'dart:isolate';
23
import 'dart:ui';
34
import 'package:flutter/material.dart';
45
import 'package:flutter_background_service/flutter_background_service.dart';
@@ -8,13 +9,16 @@ import 'package:mostro_mobile/data/repositories/event_storage.dart';
89
import 'package:mostro_mobile/features/settings/settings.dart';
910
import 'package:mostro_mobile/features/notifications/services/background_notification_service.dart' as notification_service;
1011
import 'package:mostro_mobile/services/nostr_service.dart';
12+
import 'package:mostro_mobile/services/logger_service.dart' as logger_service;
1113
import 'package:mostro_mobile/shared/providers/mostro_database_provider.dart';
1214

1315
bool isAppForeground = true;
1416
String currentLanguage = 'en';
1517

1618
@pragma('vm:entry-point')
1719
Future<void> serviceMain(ServiceInstance service) async {
20+
SendPort? loggerSendPort;
21+
Logger? logger;
1822

1923
final Map<String, Map<String, dynamic>> activeSubscriptions = {};
2024
final nostrService = NostrService();
@@ -31,6 +35,14 @@ Future<void> serviceMain(ServiceInstance service) async {
3135
final settingsMap = data['settings'];
3236
if (settingsMap == null) return;
3337

38+
loggerSendPort = data['loggerSendPort'] as SendPort?;
39+
40+
logger = Logger(
41+
printer: logger_service.SimplePrinter(),
42+
output: logger_service.IsolateLogOutput(loggerSendPort),
43+
level: Level.debug,
44+
);
45+
3446
final settings = Settings.fromJson(settingsMap);
3547
currentLanguage = settings.selectedLanguage ?? PlatformDispatcher.instance.locale.languageCode;
3648
await nostrService.init(settings);
@@ -74,7 +86,13 @@ Future<void> serviceMain(ServiceInstance service) async {
7486
}
7587
await notification_service.retryNotification(event);
7688
} catch (e) {
77-
Logger().e('Error processing event', error: e);
89+
final currentLogger = logger;
90+
if (currentLogger != null) {
91+
currentLogger.e('Error processing event', error: e);
92+
} else {
93+
// ignore: avoid_print
94+
print('ERROR (logger not ready): Error processing event: $e');
95+
}
7896
}
7997
});
8098
});

lib/background/desktop_background_service.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:logger/logger.dart';
66
import 'package:mostro_mobile/data/models/nostr_filter.dart';
77
import 'package:mostro_mobile/features/settings/settings.dart';
88
import 'package:mostro_mobile/services/nostr_service.dart';
9+
import 'package:mostro_mobile/services/logger_service.dart' as logger_service;
910
import 'abstract_background_service.dart';
1011

1112
class DesktopBackgroundService implements BackgroundService {
@@ -22,13 +23,19 @@ class DesktopBackgroundService implements BackgroundService {
2223
final isolateReceivePort = ReceivePort();
2324
final mainSendPort = args[0] as SendPort;
2425
final token = args[1] as RootIsolateToken;
26+
final loggerSendPort = args.length > 2 ? args[2] as SendPort? : null;
2527

2628
mainSendPort.send(isolateReceivePort.sendPort);
2729

2830
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
2931

32+
final logger = Logger(
33+
printer: logger_service.SimplePrinter(),
34+
output: logger_service.IsolateLogOutput(loggerSendPort),
35+
level: Level.debug,
36+
);
37+
3038
final nostrService = NostrService();
31-
final logger = Logger();
3239
bool isAppForeground = true;
3340

3441
isolateReceivePort.listen((message) async {

lib/background/mobile_background_service.dart

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import 'dart:async';
22

33
import 'package:dart_nostr/nostr/model/request/filter.dart';
44
import 'package:flutter_background_service/flutter_background_service.dart';
5-
import 'package:logger/logger.dart';
65
import 'package:mostro_mobile/background/background.dart';
76
import 'package:mostro_mobile/features/settings/settings.dart';
7+
import 'package:mostro_mobile/services/logger_service.dart' as logger_service;
88
import 'abstract_background_service.dart';
99

1010
class MobileBackgroundService implements BackgroundService {
@@ -16,7 +16,6 @@ class MobileBackgroundService implements BackgroundService {
1616

1717
final _subscriptions = <String, Map<String, dynamic>>{};
1818
bool _isRunning = false;
19-
final _logger = Logger();
2019
bool _serviceReady = false;
2120
final List<Function> _pendingOperations = [];
2221

@@ -44,19 +43,20 @@ class MobileBackgroundService implements BackgroundService {
4443
_isRunning = true;
4544
service.invoke('start', {
4645
'settings': _settings.toJson(),
46+
'loggerSendPort': logger_service.isolateLogSenderPort,
4747
});
48-
_logger.d(
48+
logger_service.logger.d(
4949
'Service started with settings: ${_settings.toJson()}',
5050
);
5151
});
5252

5353
service.on('on-stop').listen((event) {
5454
_isRunning = false;
55-
_logger.i('Service stopped');
55+
logger_service.logger.i('Service stopped');
5656
});
5757

5858
service.on('service-ready').listen((data) {
59-
_logger.i("Service confirmed it's ready");
59+
logger_service.logger.i("Service confirmed it's ready");
6060
_serviceReady = true;
6161
_processPendingOperations();
6262
});
@@ -68,7 +68,7 @@ class MobileBackgroundService implements BackgroundService {
6868
_subscriptions[subId] = {'filters': filters};
6969

7070
_executeWhenReady(() {
71-
_logger.i("Sending subscription to service");
71+
logger_service.logger.i("Sending subscription to service");
7272
service.invoke('create-subscription', {
7373
'id': subId,
7474
'filters': filters.map((f) => f.toMap()).toList(),
@@ -127,7 +127,7 @@ class MobileBackgroundService implements BackgroundService {
127127
try {
128128
await _startService();
129129
} catch (e) {
130-
_logger.e('Error starting service: $e');
130+
logger_service.logger.e('Error starting service: $e');
131131
// Retry with a delay if needed
132132
await Future.delayed(Duration(seconds: 1));
133133
await _startService();
@@ -137,7 +137,7 @@ class MobileBackgroundService implements BackgroundService {
137137
}
138138

139139
Future<void> _startService() async {
140-
_logger.i("Starting service");
140+
logger_service.logger.i("Starting service");
141141
await service.startService();
142142
_serviceReady = false; // Reset ready state when starting
143143

@@ -152,9 +152,10 @@ class MobileBackgroundService implements BackgroundService {
152152
await Future.delayed(const Duration(milliseconds: 50));
153153
}
154154

155-
_logger.i("Service running, sending settings");
155+
logger_service.logger.i("Service running, sending settings");
156156
service.invoke('start', {
157157
'settings': _settings.toJson(),
158+
'loggerSendPort': logger_service.isolateLogSenderPort,
158159
});
159160
}
160161

lib/data/repositories/mostro_storage.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import 'package:logger/logger.dart';
1+
import 'package:mostro_mobile/services/logger_service.dart';
22
import 'package:mostro_mobile/data/models/payload.dart';
33
import 'package:sembast/sembast.dart';
44
import 'package:mostro_mobile/data/models/mostro_message.dart';
55
import 'package:mostro_mobile/data/repositories/base_storage.dart';
66

77
class MostroStorage extends BaseStorage<MostroMessage> {
8-
final Logger _logger = Logger();
8+
99

1010
MostroStorage({required Database db})
1111
: super(db, stringMapStoreFactory.store('orders'));
@@ -21,11 +21,11 @@ class MostroStorage extends BaseStorage<MostroMessage> {
2121
dbMap['timestamp'] = message.timestamp;
2222

2323
await store.record(id).put(db, dbMap);
24-
_logger.i(
24+
logger.i(
2525
'Saved message of type ${message.action} with order id ${message.id}',
2626
);
2727
} catch (e, stack) {
28-
_logger.e(
28+
logger.e(
2929
'addMessage failed for $id',
3030
error: e,
3131
stackTrace: stack,
@@ -39,7 +39,7 @@ class MostroStorage extends BaseStorage<MostroMessage> {
3939
try {
4040
return await getAll();
4141
} catch (e, stack) {
42-
_logger.e('getAllMessages failed', error: e, stackTrace: stack);
42+
logger.e('getAllMessages failed', error: e, stackTrace: stack);
4343
return <MostroMessage>[];
4444
}
4545
}
@@ -48,9 +48,9 @@ class MostroStorage extends BaseStorage<MostroMessage> {
4848
Future<void> deleteAllMessages() async {
4949
try {
5050
await deleteAll();
51-
_logger.i('All messages deleted');
51+
logger.i('All messages deleted');
5252
} catch (e, stack) {
53-
_logger.e('deleteAllMessages failed', error: e, stackTrace: stack);
53+
logger.e('deleteAllMessages failed', error: e, stackTrace: stack);
5454
rethrow;
5555
}
5656
}

lib/features/order/notfiers/abstract_mostro_notifier.dart

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ import 'package:mostro_mobile/features/chat/providers/chat_room_providers.dart';
1111
import 'package:mostro_mobile/features/notifications/providers/notifications_provider.dart';
1212
import 'package:mostro_mobile/features/notifications/utils/notification_data_extractor.dart';
1313
import 'package:mostro_mobile/features/settings/settings_provider.dart';
14-
import 'package:logger/logger.dart';
14+
import 'package:mostro_mobile/services/logger_service.dart';
1515

1616
class AbstractMostroNotifier extends StateNotifier<OrderState> {
1717
final String orderId;
1818
final Ref ref;
19-
final logger = Logger();
2019

2120
late Session session;
2221

@@ -504,17 +503,17 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
504503
_sessionTimeouts[orderId] = Timer(const Duration(seconds: 10), () {
505504
try {
506505
ref.read(sessionNotifierProvider.notifier).deleteSession(orderId);
507-
Logger().i('Session cleaned up after 10s timeout: $orderId');
506+
logger.i('Session cleaned up after 10s timeout: $orderId');
508507

509508
// Show timeout message to user and navigate to order book
510509
_showTimeoutNotificationAndNavigate(ref);
511510
} catch (e) {
512-
Logger().e('Failed to cleanup session: $orderId', error: e);
511+
logger.e('Failed to cleanup session: $orderId', error: e);
513512
}
514513
_sessionTimeouts.remove(orderId);
515514
});
516515

517-
Logger().i('Started 10s timeout timer for order: $orderId');
516+
logger.i('Started 10s timeout timer for order: $orderId');
518517
}
519518

520519
/// Shows timeout notification and navigates to order book
@@ -528,7 +527,7 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
528527
final navProvider = ref.read(navigationProvider.notifier);
529528
navProvider.go('/');
530529
} catch (e) {
531-
Logger().e('Failed to show timeout notification or navigate', error: e);
530+
logger.e('Failed to show timeout notification or navigate', error: e);
532531
}
533532
}
534533

@@ -541,17 +540,17 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
541540
_sessionTimeouts[key] = Timer(const Duration(seconds: 10), () {
542541
try {
543542
ref.read(sessionNotifierProvider.notifier).deleteSessionByRequestId(requestId);
544-
Logger().i('Session cleaned up after 10s timeout for requestId: $requestId');
543+
logger.i('Session cleaned up after 10s timeout for requestId: $requestId');
545544

546545
// Show timeout message to user and navigate to order book
547546
_showTimeoutNotificationAndNavigate(ref);
548547
} catch (e) {
549-
Logger().e('Failed to cleanup session for requestId: $requestId', error: e);
548+
logger.e('Failed to cleanup session for requestId: $requestId', error: e);
550549
}
551550
_sessionTimeouts.remove(key);
552551
});
553552

554-
Logger().i('Started 10s timeout timer for requestId: $requestId');
553+
logger.i('Started 10s timeout timer for requestId: $requestId');
555554
}
556555

557556
/// Cancels the timeout timer for a specific orderId
@@ -560,7 +559,7 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
560559
if (timer != null) {
561560
timer.cancel();
562561
_sessionTimeouts.remove(orderId);
563-
Logger().i('Cancelled 10s timeout timer for order: $orderId - Mostro responded');
562+
logger.i('Cancelled 10s timeout timer for order: $orderId - Mostro responded');
564563
}
565564
}
566565

@@ -571,7 +570,7 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
571570
if (timer != null) {
572571
timer.cancel();
573572
_sessionTimeouts.remove(key);
574-
Logger().i('Cancelled 10s timeout timer for requestId: $requestId - Mostro responded');
573+
logger.i('Cancelled 10s timeout timer for requestId: $requestId - Mostro responded');
575574
}
576575
}
577576

lib/features/order/notfiers/add_order_notifier.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:mostro_mobile/shared/providers.dart';
66
import 'package:mostro_mobile/features/order/notfiers/abstract_mostro_notifier.dart';
77
import 'package:mostro_mobile/features/order/providers/order_notifier_provider.dart';
88
import 'package:mostro_mobile/features/order/models/order_state.dart';
9+
import 'package:mostro_mobile/services/logger_service.dart';
910
import 'package:mostro_mobile/services/mostro_service.dart';
1011

1112
class AddOrderNotifier extends AbstractMostroNotifier {

lib/features/order/notfiers/order_notifier.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:mostro_mobile/features/order/models/order_state.dart';
66
import 'package:mostro_mobile/features/notifications/providers/notifications_provider.dart';
77
import 'package:mostro_mobile/shared/providers.dart';
88
import 'package:mostro_mobile/features/order/notfiers/abstract_mostro_notifier.dart';
9+
import 'package:mostro_mobile/services/logger_service.dart';
910
import 'package:mostro_mobile/services/mostro_service.dart';
1011

1112
class OrderNotifier extends AbstractMostroNotifier {

lib/main.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ import 'package:mostro_mobile/shared/providers/background_service_provider.dart'
1616
import 'package:mostro_mobile/shared/providers/providers.dart';
1717
import 'package:mostro_mobile/shared/utils/biometrics_helper.dart';
1818
import 'package:mostro_mobile/shared/utils/notification_permission_helper.dart';
19+
import 'package:mostro_mobile/services/logger_service.dart';
1920
import 'package:shared_preferences/shared_preferences.dart';
2021
import 'package:timeago/timeago.dart' as timeago;
2122

2223
Future<void> main() async {
2324
WidgetsFlutterBinding.ensureInitialized();
2425

26+
initIsolateLogReceiver();
27+
2528
await requestNotificationPermissionIfNeeded();
2629

2730
final biometricsHelper = BiometricsHelper();

0 commit comments

Comments
 (0)