Files
AutoJudge-Flutter/lib/utils/app_logger.dart
2025-11-13 09:14:49 +08:00

240 lines
5.9 KiB
Dart

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<String, dynamic>? 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<String, dynamic> _sanitizeHeaders(Map<String, dynamic> headers) {
final sanitized = Map<String, dynamic>.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<String, dynamic>.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;
}
}
}