Files
plezy/lib/utils/app_logger.dart
T

172 lines
5.1 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 to wrapped printer for console output
return _wrappedPrinter.log(event);
}
}
/// 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;
}