import 'package:flutter/foundation.dart'; import 'package:logger/logger.dart'; /// Application-wide logger with configurable levels and output class AppLogger { static final AppLogger _instance = AppLogger._internal(); factory AppLogger() => _instance; late final Logger _logger; bool _isInitialized = false; AppLogger._internal(); /// Initialize the logger with appropriate settings void initialize({Level? level, LogOutput? output, LogFilter? filter}) { if (_isInitialized) return; _logger = Logger( level: level ?? _getDefaultLevel(), filter: filter ?? ProductionFilter(), printer: _createPrinter(), output: output ?? _createOutput(), ); _isInitialized = true; } /// Get default log level based on build mode Level _getDefaultLevel() { if (kDebugMode) { return Level.debug; } else if (kProfileMode) { return Level.info; } else { return Level.warning; } } /// Create appropriate printer based on build mode LogPrinter _createPrinter() { if (kDebugMode) { // Detailed printer for development return PrettyPrinter( methodCount: 2, errorMethodCount: 8, lineLength: 120, colors: true, printEmojis: true, dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart, ); } else { // Simple printer for production return SimplePrinter(colors: false); } } /// Create appropriate output based on build mode LogOutput _createOutput() { if (kReleaseMode) { // In production, you might want to send logs to a file or remote service return ConsoleOutput(); } else { return ConsoleOutput(); } } /// Ensure logger is initialized void _ensureInitialized() { if (!_isInitialized) { initialize(); } } /// Log a trace message (most verbose) void trace(String message, {Object? error, StackTrace? stackTrace}) { _ensureInitialized(); _logger.t(message, error: error, stackTrace: stackTrace); } /// Log a debug message void debug(String message, {Object? error, StackTrace? stackTrace}) { _ensureInitialized(); _logger.d(message, error: error, stackTrace: stackTrace); } /// Log an info message void info(String message, {Object? error, StackTrace? stackTrace}) { _ensureInitialized(); _logger.i(message, error: error, stackTrace: stackTrace); } /// Log a warning message void warning(String message, {Object? error, StackTrace? stackTrace}) { _ensureInitialized(); _logger.w(message, error: error, stackTrace: stackTrace); } /// Log an error message void error(String message, {Object? error, StackTrace? stackTrace}) { _ensureInitialized(); _logger.e(message, error: error, stackTrace: stackTrace); } /// Log a fatal error message void fatal(String message, {Object? error, StackTrace? stackTrace}) { _ensureInitialized(); _logger.f(message, error: error, stackTrace: stackTrace); } /// Log network request void logRequest( String method, String url, { Map? headers, dynamic body, }) { if (!kReleaseMode) { _ensureInitialized(); final buffer = StringBuffer(); buffer.writeln('→ $method $url'); if (headers != null && headers.isNotEmpty) { buffer.writeln('Headers: ${_sanitizeHeaders(headers)}'); } if (body != null) { buffer.writeln('Body: ${_sanitizeBody(body)}'); } _logger.d(buffer.toString()); } } /// Log network response void logResponse( String method, String url, int statusCode, { dynamic body, Duration? duration, }) { if (!kReleaseMode) { _ensureInitialized(); final buffer = StringBuffer(); buffer.write('← $method $url [$statusCode]'); if (duration != null) { buffer.write(' (${duration.inMilliseconds}ms)'); } buffer.writeln(); if (body != null) { buffer.writeln('Body: ${_sanitizeBody(body)}'); } if (statusCode >= 200 && statusCode < 300) { _logger.d(buffer.toString()); } else if (statusCode >= 400) { _logger.e(buffer.toString()); } else { _logger.w(buffer.toString()); } } } /// Sanitize headers to remove sensitive information Map _sanitizeHeaders(Map headers) { final sanitized = Map.from(headers); // List of sensitive header keys to redact const sensitiveKeys = [ 'authorization', 'cookie', 'set-cookie', 'x-api-key', 'x-auth-token', ]; for (final key in sensitiveKeys) { if (sanitized.containsKey(key.toLowerCase())) { sanitized[key] = '***REDACTED***'; } } return sanitized; } /// Sanitize body to remove sensitive information dynamic _sanitizeBody(dynamic body) { if (body is Map) { final sanitized = Map.from(body); // List of sensitive field names to redact const sensitiveFields = [ 'password', 'pwd', 'passwd', 'token', 'secret', 'apiKey', 'api_key', ]; for (final field in sensitiveFields) { if (sanitized.containsKey(field)) { sanitized[field] = '***REDACTED***'; } } return sanitized; } // For string bodies, check if it contains sensitive patterns if (body is String) { if (body.length > 1000) { return '${body.substring(0, 1000)}... (truncated)'; } // Redact potential passwords in query strings or form data return body.replaceAllMapped( RegExp( r'(password|pwd|passwd|token|secret)=[^&\s]+', caseSensitive: false, ), (match) => '${match.group(1)}=***REDACTED***', ); } return body; } /// Close the logger and release resources void close() { if (_isInitialized) { _logger.close(); _isInitialized = false; } } }