import 'dart:collection'; import 'package:logger/logger.dart'; import 'log_redaction_manager.dart'; /// Redacts sensitive information from log messages based on known values. String _redactSensitiveData(String message) { var redacted = LogRedactionManager.redact(message); // Fallbacks for sensitive fields we cannot track ahead of time. redacted = redacted.replaceAllMapped( RegExp(r'([Aa]uthorization[=:]\s*)([^\s,]+)'), (match) => '${match.group(1)}[REDACTED]', ); redacted = redacted.replaceAllMapped( RegExp(r'([Pp]assword[=:]\s*)([^\s&,;]+)'), (match) => '${match.group(1)}[REDACTED]', ); return redacted; } /// Represents a single log entry stored in memory class LogEntry { final DateTime timestamp; final Level level; final String message; final Object? error; final StackTrace? stackTrace; const LogEntry({required this.timestamp, required this.level, required this.message, this.error, this.stackTrace}); /// Estimate the memory size of this log entry in bytes int get estimatedSize { int size = 0; size += 8; size += 4; size += message.length * 2; if (error != null) { size += error.toString().length * 2; } if (stackTrace != null) { size += stackTrace.toString().length * 2; } return size; } } /// Custom log output that stores logs in memory with a circular buffer /// /// Storage is handled by [MemoryAwareLogPrinter.log()] — this class only /// forwards formatted lines to the console via the default [ConsoleOutput]. class MemoryLogOutput extends LogOutput { static const int maxLogSizeBytes = 5 * 1024 * 1024; static final ListQueue _logs = ListQueue(); static int _currentSize = 0; static final _consoleOutput = ConsoleOutput(); static List getLogs() => _logs.toList().reversed.toList(); static void clearLogs() { _logs.clear(); _currentSize = 0; } static int getCurrentSize() => _currentSize; static double getCurrentSizeMB() => _currentSize / (1024 * 1024); @override void output(OutputEvent event) { // Only print to console — storage is done in MemoryAwareLogPrinter.log() _consoleOutput.output(event); } } /// Custom log printer that also stores error and stack trace information class MemoryAwareLogPrinter extends LogPrinter { final LogPrinter _wrappedPrinter; MemoryAwareLogPrinter(this._wrappedPrinter); @override List log(LogEvent event) { // Store the log with error and stack trace if available final message = _redactSensitiveData(event.message.toString()); final error = event.error != null ? _redactSensitiveData(event.error.toString()) : null; final logEntry = LogEntry( timestamp: DateTime.now(), level: event.level, message: message, error: error, stackTrace: event.stackTrace, ); MemoryLogOutput._logs.add(logEntry); MemoryLogOutput._currentSize += logEntry.estimatedSize; // Maintain buffer size limit (remove oldest entries) — O(1) with ListQueue while (MemoryLogOutput._currentSize > MemoryLogOutput.maxLogSizeBytes && MemoryLogOutput._logs.isNotEmpty) { final removed = MemoryLogOutput._logs.removeFirst(); MemoryLogOutput._currentSize -= removed.estimatedSize; } return _wrappedPrinter.log( LogEvent(event.level, message, time: event.time, error: error, stackTrace: event.stackTrace), ); } } /// Custom production filter that respects our level setting even in release mode class ProductionFilter extends LogFilter { Level _currentLevel = Level.debug; void setLevel(Level level) { _currentLevel = level; } @override bool shouldLog(LogEvent event) { return event.level.value >= _currentLevel.value; } } final _productionFilter = ProductionFilter(); /// Centralized logger instance for the application. /// /// Usage: /// ```dart /// import 'package:plezy/utils/app_logger.dart'; /// /// appLogger.d('Debug message'); /// appLogger.i('Info message'); /// appLogger.w('Warning message'); /// appLogger.e('Error message', error: e, stackTrace: stackTrace); /// ``` Logger appLogger = Logger( printer: MemoryAwareLogPrinter(SimplePrinter()), filter: _productionFilter, level: Level.debug, ); /// Update the logger's level dynamically based on debug setting /// Recreates the logger instance to ensure it works in release mode void setLoggerLevel(bool debugEnabled) { final newLevel = debugEnabled ? Level.debug : Level.info; _productionFilter.setLevel(newLevel); appLogger = Logger(printer: MemoryAwareLogPrinter(SimplePrinter()), filter: _productionFilter, level: newLevel); Logger.level = newLevel; }