|
| 1 | +# In-App Logging System Implementation |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Implementation of a comprehensive logging system for MostroP2P mobile app with in-memory capture, background isolate support, privacy sanitization, and export capabilities. |
| 6 | + |
| 7 | +## Design Approach |
| 8 | + |
| 9 | +### Why This Architecture? |
| 10 | + |
| 11 | +This implementation uses a **singleton logger pattern with centralized output management** rather than individual logger instances per service. While this approach requires updating all logging calls across the codebase, it provides critical advantages: |
| 12 | + |
| 13 | +**1. Guaranteed Log Capture** |
| 14 | +- Single source of truth ensures NO logs are missed |
| 15 | +- All application logs automatically captured in memory |
| 16 | +- Background isolates seamlessly integrated via SendPort |
| 17 | + |
| 18 | +**2. Consistent Privacy Protection** |
| 19 | +- Centralized sanitization means sensitive data CANNOT leak |
| 20 | +- Single `cleanMessage()` function applies to all logs uniformly |
| 21 | +- No risk of individual services bypassing sanitization |
| 22 | + |
| 23 | +**3. Memory Safety** |
| 24 | +- Centralized buffer with strict size limits prevents OOM issues |
| 25 | +- Batch deletion strategy maintains performance |
| 26 | +- Predictable memory footprint regardless of log volume |
| 27 | + |
| 28 | +**4. Development Experience** |
| 29 | +- Simple API: `logger.i()`, `logger.e()`, `logger.d()` |
| 30 | +- No configuration needed per service |
| 31 | +- Stack trace extraction for debugging |
| 32 | +- Maintains normal console output while providing a minimized log view for the UI |
| 33 | + |
| 34 | +### Trade-offs |
| 35 | + |
| 36 | +**Cons of Singleton Approach**: |
| 37 | +- Requires updating ~30+ files to replace `print()` calls |
| 38 | +- One-time migration effort across codebase |
| 39 | + |
| 40 | +## Architecture |
| 41 | + |
| 42 | +``` |
| 43 | +Main Isolate: |
| 44 | + logger (singleton) → MemoryLogOutput → UI (LogsScreen) |
| 45 | + ↘ ConsoleOutput |
| 46 | +
|
| 47 | +Background Isolate: |
| 48 | + logger → IsolateLogOutput → SendPort → Main Isolate ReceivePort → MemoryLogOutput |
| 49 | +``` |
| 50 | + |
| 51 | +## Critical Implementation Details |
| 52 | + |
| 53 | +### 1. Logger Usage Pattern |
| 54 | + |
| 55 | +**IMPORTANT**: Always use the singleton `logger` instance, never instantiate `Logger()` directly. |
| 56 | + |
| 57 | +```dart |
| 58 | +// CORRECT |
| 59 | +import 'package:mostro_mobile/services/logger_service.dart'; |
| 60 | +
|
| 61 | +logger.i('Info message'); |
| 62 | +logger.e('Error occurred', error: e, stackTrace: stack); |
| 63 | +
|
| 64 | +// WRONG - Do not instantiate directly |
| 65 | +final myLogger = Logger(); |
| 66 | +``` |
| 67 | + |
| 68 | +### 2. Configuration (`lib/core/config.dart`) |
| 69 | + |
| 70 | + |
| 71 | +```dart |
| 72 | +// Logger configuration |
| 73 | +static const int logMaxEntries = 1000; // Maximum logs in memory |
| 74 | +static const int logBatchDeleteSize = 100; // Batch delete when limit exceeded |
| 75 | +static bool fullLogsInfo = true; // true = PrettyPrinter, false = SimplePrinter |
| 76 | +``` |
| 77 | + |
| 78 | +**Buffer Management**: |
| 79 | +- When buffer exceeds `logMaxEntries`, oldest `logBatchDeleteSize` entries are deleted |
| 80 | +- FIFO queue ensures recent logs are preserved |
| 81 | +- Prevents memory exhaustion from log flooding |
| 82 | + |
| 83 | +### 3. Background Isolate Integration |
| 84 | + |
| 85 | +Background services MUST initialize logging with `IsolateLogOutput`: |
| 86 | + |
| 87 | +```dart |
| 88 | +@pragma('vm:entry-point') |
| 89 | +void backgroundServiceEntryPoint(SendPort sendPort) async { |
| 90 | + // Get log sender from main isolate |
| 91 | + final logSender = isolateLogSenderPort; |
| 92 | +
|
| 93 | + // Initialize logger for this isolate |
| 94 | + Logger.level = Level.debug; |
| 95 | + final logger = Logger( |
| 96 | + printer: SimplePrinter(), |
| 97 | + output: IsolateLogOutput(logSender), |
| 98 | + ); |
| 99 | +
|
| 100 | + // Now logs from this isolate appear in main app |
| 101 | + logger.i('Background service started'); |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +**Main isolate setup** (in `main.dart`): |
| 106 | +```dart |
| 107 | +void main() async { |
| 108 | + WidgetsFlutterBinding.ensureInitialized(); |
| 109 | +
|
| 110 | + // Initialize log receiver BEFORE starting background services |
| 111 | + initIsolateLogReceiver(); |
| 112 | +
|
| 113 | + // ... rest of initialization |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +### 4. Privacy Sanitization |
| 118 | + |
| 119 | +The `cleanMessage()` function automatically redacts: |
| 120 | + |
| 121 | +| Pattern | Replacement | Example | |
| 122 | +|---------|-------------|---------| |
| 123 | +| `nsec[0-9a-z]+` | `[PRIVATE_KEY]` | `nsec1abc...` → `[PRIVATE_KEY]` | |
| 124 | +| `"privateKey":"..."` | `"privateKey":"[REDACTED]"` | JSON field redacted | |
| 125 | +| `"mnemonic":"..."` | `"mnemonic":"[REDACTED]"` | Seed phrase redacted | |
| 126 | +| ANSI codes | Removed | Color/formatting stripped | |
| 127 | +| Emojis | Removed | All emoji ranges | |
| 128 | + |
| 129 | +### 5. File Storage & Permissions |
| 130 | + |
| 131 | +**Android/iOS**: |
| 132 | +```dart |
| 133 | +// External storage (requires permissions in future) |
| 134 | +final directory = await getExternalStorageDirectory(); |
| 135 | +final logsDir = Directory('${directory.path}/MostroLogs'); |
| 136 | +``` |
| 137 | + |
| 138 | +**PERMISSIONS REQUIRED** (not yet implemented): |
| 139 | +- Android: `WRITE_EXTERNAL_STORAGE` permission |
| 140 | +- iOS: Automatic with app sandbox |
| 141 | + |
| 142 | +**Desktop/Web**: |
| 143 | +```dart |
| 144 | +// Application documents (no special permissions) |
| 145 | +final directory = await getApplicationDocumentsDirectory(); |
| 146 | +``` |
| 147 | + |
| 148 | +**Storage location display**: |
| 149 | +- UI shows storage path to user |
| 150 | +- Configurable via `Settings.customLogStorageDirectory` |
| 151 | + |
| 152 | +## Migration Example |
| 153 | + |
| 154 | +### Service Integration |
| 155 | + |
| 156 | +```dart |
| 157 | +// lib/services/nostr_service.dart |
| 158 | +
|
| 159 | +import 'package:mostro_mobile/services/logger_service.dart'; |
| 160 | +
|
| 161 | +class NostrService { |
| 162 | + Future<void> connect() async { |
| 163 | + logger.i('Connecting to Nostr relays'); |
| 164 | +
|
| 165 | + try { |
| 166 | + await _connectToRelays(); |
| 167 | + logger.i('Successfully connected to ${relays.length} relays'); |
| 168 | + } catch (e, stack) { |
| 169 | + logger.e('Failed to connect to relays', error: e, stackTrace: stack); |
| 170 | + rethrow; |
| 171 | + } |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +### Background Service |
| 177 | + |
| 178 | +```dart |
| 179 | +// lib/background/mobile_background_service.dart |
| 180 | +
|
| 181 | +@pragma('vm:entry-point') |
| 182 | +void backgroundMain(SendPort sendPort) async { |
| 183 | + final logSender = isolateLogSenderPort; |
| 184 | + final logger = Logger( |
| 185 | + printer: SimplePrinter(), |
| 186 | + output: IsolateLogOutput(logSender), |
| 187 | + ); |
| 188 | +
|
| 189 | + logger.i('Background service started'); |
| 190 | +
|
| 191 | + try { |
| 192 | + await performBackgroundTask(); |
| 193 | + } catch (e, stack) { |
| 194 | + logger.e('Background task failed', error: e, stackTrace: stack); |
| 195 | + } |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | + |
| 200 | +--- |
| 201 | + |
| 202 | +**Version**: 1.0 |
| 203 | +**Status**: Phase 1 - Planning Complete |
0 commit comments