# Logging Guidelines > How logging is done in Flutter app. --- ## Overview This app uses **structured logging** with custom `Logger` class: - **Library**: Custom `Logger` class in `core/logging/` - **Interface**: `getLogger(module)` from `core/logging/logger.dart` - **Log levels**: `debug`, `info`, `warning`, `error` - **Sensitive fields**: Never log passwords, tokens, PII --- ## Logger Setup ### Import and Initialize ```dart import 'core/logging/logger.dart'; class SomeBloc extends ChangeNotifier { final Logger _logger = getLogger('features.auth.bloc'); } ``` ### Module Naming Convention | Feature | Module Path | |---------|------------| | auth | `features.auth` | | home | `features.home` | | divination | `features.divination` | | settings | `features.settings` | **Examples:** ```dart // features/auth/presentation/bloc/auth_bloc.dart final Logger _logger = getLogger('features.auth.bloc'); // features/home/data/repositories/home_repository.dart final Logger _logger = getLogger('features.home.repository'); // core/network/http_client.dart final Logger _logger = getLogger('core.network.http_client'); ``` --- ## Log Levels | Level | When to Use | Noise Level | Required | |-------|-------------|-------------|----------| | **error** | All exceptions and failures | Required | Never skip | | **warning** | Degraded behavior, retry, fallback | Minimal | Only when action taken | | **info** | Key business events | Minimal | Only milestones | | **debug** | Detailed flow tracing (dev only) | High | Avoid in release | --- ## Error Logging Requirements ### Every try-catch MUST log the exception: ```dart try { await _repository.someOperation(); } catch (e, stackTrace) { _logger.error( message: 'Operation failed: $operationName', error: e, stackTrace: stackTrace, extra: {'context': 'relevant_data'}, ); // handle error } ``` ### Error Logging Pattern ```dart // features/auth/presentation/bloc/auth_bloc.dart Future start() async { try { final user = await _repository.recoverSession(); // ... } catch (error, stackTrace) { _logger.error( message: 'Session recovery failed: ${error.runtimeType}', error: error.runtimeType.toString(), stackTrace: stackTrace, ); await _repository.clearLocalSession(); _state = AuthState( status: AuthStatus.unauthenticated, errorMessage: _toSafeMessage(error), ); notifyListeners(); } } ``` --- ## Info Logging Requirements ### Only log these milestone events: - User login/logout - Message sent/received - Data sync completed - Important state transitions ### Info Logging Pattern ```dart // Login success _logger.info( message: 'User logged in', extra: {'user_id': user.id}, ); // Run success _logger.info( message: 'Run completed', extra: { 'run_id': runId, 'thread_id': threadId, 'duration_ms': duration.inMilliseconds, }, ); ``` ### ❌ DO NOT log for every operation: ```dart // WRONG: Logging every keystroke onChanged: (value) { _logger.info('Input changed: $value'); // Too noisy } ``` --- ## Warning Logging Requirements ### Only log when taking corrective action: - Retrying after failure - Using fallback data - Skipping malformed data - Deprecation warnings ### Warning Logging Pattern ```dart // Cache miss with fallback _logger.warning( message: 'Cache miss, loading from remote', extra: {'key': cacheKey}, ); // Retry attempt _logger.warning( message: 'Retry attempt', extra: {'attempt': 2, 'max_attempts': 3}, ); // Fallback data _logger.warning( message: 'Using fallback data due to network timeout', extra: {'timeout_ms': 5000}, ); ``` --- ## Debug Logging ### Use sparingly, only in debug builds: ```dart if (kDebugMode) { _logger.debug( message: 'Variable value', extra: {'variable': expensiveObject.toString()}, ); } ``` **Note:** Debug logs are automatically filtered in release builds. --- ## Prohibited Practices ### ❌ Never log sensitive data ```dart // WRONG: Logging password _logger.info(message: 'User login', extra: {'password': password}); // WRONG: Logging token _logger.debug(message: 'API call', extra: {'token': accessToken}); // WRONG: Logging PII _logger.info(message: 'User profile', extra: {'email': userEmail}); ``` ### ❌ Never log at debug level in production ```dart // WRONG: Will log in release build _logger.debug(message: 'Debug info: $sensitiveData'); ``` **Right way:** Use `kDebugMode` guard: ```dart if (kDebugMode) { _logger.debug(message: 'Variable value', extra: {'var': value}); } ``` ### ❌ Never skip error logging ```dart // WRONG: Exception is caught but not logged try { await operation(); } catch (e) { state = ErrorState(); // Missing error log! } ``` ### ❌ Never log in every iteration ```dart // WRONG: Log every iteration for (item in items) { _logger.debug('Processing item: ${item.id}'); // Too noisy process(item); } ``` **Right: Log only failures:** ```dart for (item in items) { try { process(item); } catch (e, stackTrace) { _logger.error( message: 'Item processing failed', error: e, stackTrace: stackTrace, extra: {'item_id': item.id}, ); } } ``` --- ## Logger Implementation ### Core Logger Class ```dart // core/logging/logger.dart import 'log_entry.dart'; import 'log_service.dart'; class Logger { final String module; final LogService? _service; final bool _isNoOp; Logger(this.module, this._service) : _isNoOp = _service == null; factory Logger.get(String module) { return Logger(module, _globalLogService); } void error({ required String message, required Object error, required StackTrace stackTrace, Map? extra, }) { if (_isNoOp) { debugPrint(LogEntry( message: message, module: module, errorType: error.runtimeType.toString(), ).toConsoleString()); return; } _service!.error( message: message, error: error, stackTrace: stackTrace, module: module, extra: extra ?? {}, ); } void info({ required String message, Map? extra, }) { if (_isNoOp) return; _service!.info( message: message, module: module, extra: extra ?? {}, ); } void warning({ required String message, Map? extra, }) { if (_isNoOp) return; _service!.warning( message: message, module: module, extra: extra ?? {}, ); } void debug({ required String message, Map? extra, }) { if (_isNoOp || !kDebugMode) return; _service!.debug( message: message, module: module, extra: extra ?? {}, ); } } Logger getLogger(String module) => Logger.get(module); ``` --- ## Log Entry Structure ### LogEntry Fields ```dart // core/logging/log_entry.dart class LogEntry { final DateTime timestamp; final LogLevel level; final String message; final String module; final String? errorType; final String? errorMessage; final String? stackTrace; final Map? extra; } enum LogLevel { debug, info, warning, error } ``` ### Log Format ```json { "timestamp": "2026-04-10T12:34:56.789Z", "level": "error", "module": "features.auth.bloc", "message": "Session recovery failed: SocketException", "error_type": "SocketException", "error_message": "Connection refused", "stack_trace": "...", "extra": { "user_id": "123e4567-e89b-12d3-a456-426614174000" } } ``` --- ## Common Mistakes ### ❌ Using `print()` instead of Logger ```dart // WRONG: Never use print in production code print('User logged in: $userId'); ``` **Right:** Use `Logger`: ```dart _logger.info(message: 'User logged in', extra: {'user_id': userId}); ``` ### ❌ Logging sensitive data ```dart // WRONG: Logging PII _logger.info(message: 'User profile', extra: { 'email': userEmail, 'phone': userPhone, }); ``` **Right:** Exclude sensitive fields: ```dart _logger.info(message: 'User profile loaded', extra: {'user_id': userId}); ``` ### ❌ Catching without logging ```dart // WRONG: Silent failure try { await service.doSomething(); } catch (e) { // No logging, no re-raise return null; } ``` **Right:** Log and re-raise: ```dart try { await service.doSomething(); } catch (e, stackTrace) { _logger.error( message: 'Operation failed', error: e, stackTrace: stackTrace, ); rethrow; } ``` --- ## Examples from Codebase ### AuthBloc Error Handling ```dart // features/auth/presentation/bloc/auth_bloc.dart class AuthBloc extends ChangeNotifier { final Logger _logger = getLogger('features.auth.bloc'); Future start() async { try { final user = await _repository.recoverSession(); if (user == null) { _state = const AuthState(status: AuthStatus.unauthenticated); } else { _state = AuthState(status: AuthStatus.authenticated, user: user); } notifyListeners(); } catch (error, stackTrace) { _logger.error( message: 'Session recovery failed: ${error.runtimeType}', error: error.runtimeType.toString(), stackTrace: stackTrace, ); await _repository.clearLocalSession(); _state = AuthState( status: AuthStatus.unauthenticated, errorMessage: _toSafeMessage(error), ); notifyListeners(); } } Future logout() async { _logger.info(message: 'User logged out'); _state = const AuthState(status: AuthStatus.unauthenticated); notifyListeners(); unawaited( _repository.logout().catchError((Object error, StackTrace stackTrace) { _logger.error( message: 'User logout failed: ${error.runtimeType}', error: error.runtimeType.toString(), stackTrace: stackTrace, ); }), ); } } ``` ### Network Error Handling ```dart // core/network/http_client.dart final Logger _logger = getLogger('core.network.http_client'); Future get(String path) async { try { final response = await _innerClient.get(path); final problem = ApiProblemMapper.tryParse(response); if (problem != null && response.statusCode != 401) { _logger.warning( message: 'HTTP error response', extra: { 'status': response.statusCode, 'path': path, 'code': problem.code, }, ); } return response; } on SocketException catch (e, stackTrace) { _logger.error( message: 'Network error', error: e, stackTrace: stackTrace, extra: {'path': path}, ); throw ApiProblem( status: 0, title: 'Network Error', detail: 'No internet connection', ); } } ```