174 lines
5.3 KiB
Dart
174 lines
5.3 KiB
Dart
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;
|
|
|
|
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;
|
|
// DateTime: ~8 bytes
|
|
size += 8;
|
|
// Level enum: ~4 bytes
|
|
size += 4;
|
|
// Message string: 2 bytes per character (UTF-16)
|
|
size += message.length * 2;
|
|
// Error string: 2 bytes per character if present
|
|
if (error != null) {
|
|
size += error.toString().length * 2;
|
|
}
|
|
// Stack trace string: 2 bytes per character if present
|
|
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; // 5 MB
|
|
static final ListQueue<LogEntry> _logs = ListQueue<LogEntry>();
|
|
static int _currentSize = 0;
|
|
|
|
static final _consoleOutput = ConsoleOutput();
|
|
|
|
/// Get all stored logs (newest first)
|
|
static List<LogEntry> getLogs() => _logs.toList().reversed.toList();
|
|
|
|
/// Clear all stored logs
|
|
static void clearLogs() {
|
|
_logs.clear();
|
|
_currentSize = 0;
|
|
}
|
|
|
|
/// Get current log buffer size in bytes
|
|
static int getCurrentSize() => _currentSize;
|
|
|
|
/// Get current log buffer size in MB
|
|
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<String> 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;
|
|
}
|
|
|
|
// Delegate a redacted event to the wrapped printer for console output.
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// Global filter instance
|
|
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;
|
|
|
|
// Update the filter level
|
|
_productionFilter.setLevel(newLevel);
|
|
|
|
// Recreate the logger instance with the new level
|
|
// This ensures it works in release mode where Logger.level might be optimized away
|
|
appLogger = Logger(printer: MemoryAwareLogPrinter(SimplePrinter()), filter: _productionFilter, level: newLevel);
|
|
|
|
// Also set the static level for consistency
|
|
Logger.level = newLevel;
|
|
}
|