1. 设计目标
1.1 核心目标
1.2 功能特性
graph TD
A[DSLogger功能] --> B1[基础功能]
A --> B2[存储功能]
A --> B3[扩展功能]
B1 --> C1[日志分级]
B1 --> C2[标签支持]
B1 --> C3[异常追踪]
B2 --> D1[文件存储]
B2 --> D2[自动分片]
B2 --> D3[定期清理]
B3 --> E1[长文本处理]
B3 --> E2[Base64预览]
B3 --> E3[控制台美化]
2. 系统设计
2.1 整体架构
graph TD
A[应用层] --> B[DSLogger]
B --> C1[日志处理]
B --> C2[文件管理]
B --> C3[缓存管理]
C1 --> D1[格式化]
C1 --> D2[过滤]
C2 --> D3[写入]
C2 --> D4[清理]
2.2 包结构
lib/
├── core/
│ ├── ds_logger.dart
│ ├── log_config.dart
│ └── log_level.dart
├── formatter/
│ ├── base_formatter.dart
│ ├── console_formatter.dart
│ └── file_formatter.dart
├── writer/
│ ├── base_writer.dart
│ ├── console_writer.dart
│ └── file_writer.dart
├── buffer/
│ ├── log_buffer.dart
│ └── buffer_manager.dart
└── utils/
├── file_utils.dart
└── format_utils.dart
2.3 核心类实现
2.3.1 日志记录器
class DSLogger {
static final DSLogger _instance = DSLogger._internal();
factory DSLogger() => _instance;
late final LogConfig _config;
late final LogBuffer _buffer;
late final List<LogWriter> _writers;
late final LogFormatter _formatter;
Future<void> init(LogConfig config) async {
_config = config;
_buffer = LogBuffer(
maxSize: config.bufferSize,
flushInterval: config.flushInterval,
);
_writers = [
if (config.enableConsole) ConsoleWriter(),
if (config.enableFile)
await FileWriter.initialize(
directory: config.logDir,
maxFileSize: config.maxFileSize,
maxFiles: config.maxFiles,
),
];
_formatter = config.formatter ?? DefaultFormatter();
}
void log(
LogLevel level,
String message, {
String? tag,
dynamic error,
StackTrace? stackTrace,
}) {
if (level.index < _config.minLevel.index) return;
final record = LogRecord(
level: level,
message: message,
tag: tag,
error: error,
stackTrace: stackTrace ?? StackTrace.current,
timestamp: DateTime.now(),
);
_buffer.add(record);
}
void v(String message, {String? tag, dynamic error}) =>
log(LogLevel.verbose, message, tag: tag, error: error);
void d(String message, {String? tag, dynamic error}) =>
log(LogLevel.debug, message, tag: tag, error: error);
2.3.2 文件写入器
class FileWriter implements LogWriter {
final String directory;
final int maxFileSize;
final int maxFiles;
File? _currentFile;
int _currentFileSize = 0;
static Future<FileWriter> initialize({
required String directory,
required int maxFileSize,
required int maxFiles,
}) async {
final writer = FileWriter._(
directory: directory,
maxFileSize: maxFileSize,
maxFiles: maxFiles,
);
await writer._initializeDirectory();
await writer._setupCurrentFile();
return writer;
}
Future<void> write(List<LogRecord> records) async {
final content = records.map(_formatter.format).join('\n');
if (_shouldRotateFile(_currentFileSize + content.length)) {
await _rotateFiles();
}
await _currentFile?.writeAsString(
'$content\n',
mode: FileMode.append,
flush: true,
);
_currentFileSize += content.length;
}
Future<void> _rotateFiles() async {
}
2.3.3 缓冲管理器
class LogBuffer {
final int maxSize
final Duration flushInterval;
final Queue<LogRecord> _buffer = Queue();
Timer? _flushTimer;
LogBuffer({
required this.maxSize,
required this.flushInterval,
}) {
_startFlushTimer();
}
void add(LogRecord record) {
_buffer.add(record);
if (_buffer.length >= maxSize) {
flush();
}
}
Future<void> flush() async {
if (_buffer.isEmpty) return;
final records = List<LogRecord>.from(_buffer);
_buffer.clear();
for (final writer in DSLogger().writers) {
try {
await writer.write(records);
} catch (e, stack) {
print('Error writing logs: $e\n$stack');
}
}
}
void _startFlushTimer() {
_flushTimer?.cancel();
_flushTimer = Timer.periodic(flushInterval, (_) => flush());
}
3. 关键实现
3.1 日志格式化
class ConsoleFormatter implements LogFormatter {
static const _levelColors = {
LogLevel.verbose: '\x1B[37m',
LogLevel.debug: '\x16m',
LogLevel.info: '\x1B[32m',
LogLevel.warning: '\x1B[33m',
LogLevel.error: '\x1B[31m',
};
@override
String format(LogRecord record) {
final time = _formatTime(record.timestamp);
final level = _formatLevel(record.level);
final tag = record.tag != null ? '[${record.tag}] ' : '';
var message = '$time $level $tag${record.message}';
if (record.error != null) {
message += '\nError: ${record.error}';
}
if (record.stackTrace != null) {
message += '\nStack trace:\n${record.stackTrace}';
}
return message;
}
String _formatTime(DateTime time) =>
'${time.hour.toString().padLeft(2, '0')}:'
'${time.minute.toString().padLeft(2, '0')}:'
'${time.second.toString().padLeft(2, '0')}.'
'${time.millisecond.toString().padLeft(3, '0')}';
String _formatLevel(LogLevel level) {
final color = _levelColors[level] ?? '';
final reset = '\x1B[0m';
return '$color[${level.name.toUpperCase()}]$reset';
}
3.2 文件管理
class LogFileManager {
static Future<List<File>> getLogiles(String directory) async {
final dir = Directory(directory);
if (!await dir.exists()) return [];
final files = await dir
.list()
.where((entity) => entity is File && entity.path.endsWith('.log'))
.cast<File>()
.toList();
files.sort((a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync()));
return files;
}
static Future<void> cleanOldFiles(
String directory,
int maxFiles,
) async {
final files = await getLogFiles(directory);
if (files.length > maxFiles) {
final filesToDelete = files.sublist(maxFiles);
for (final file in filesToDelete) {
await file.delete();
}
}
}
static String generateFileName() {
final now = DateTime.now();
return 'log_${now.year}${now.month}${now.day}_'
'${now.hour}${now.minute}${now.second}.log';
}
4. 性能优化
4.1 写入优化
class BatchWriter {
staticconst int _maxBatchSize = 100;
static const Duration _maxDelay = Duration(seconds: 1);
final Queue<LogRecord> _batch = Queue();
Timer? _batchTimer;
void add(LogRecord record) {
_batch.add(record);
_scheduleBatchProcess();
}
void _scheduleBatchProcess() {
if (_batch.length >= _maxBatchSize) {
_processBatch();
} else if (_batchTimer == null) {
_batchTimer = Timer(_maxDelay, _processBatch);
}
}
Future<void> _processBatch() async {
_batchTimer?.cancel();
_batchTimer = null;
if (_batch.isEmpty) return;
final records = List<LogRecord>.from(_batch);
_batch.clear();
await _writeBatch(records);
}
5. 应用集成
5.1 初始化配置
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await DSLogger.init(
LogConfig(
minLevel: LogLevel.debug,
logDir: await _getLogDirectory(),
maxFileSize: 10 * 1024 * 1024,
maxFiles: 5,
enableConsole: true,
bufferSize: 100,
flushInterval: Duration(seconds: 1),
),
);
runApp(MyApp());
}
Future<String> _getLogDirectory() async {
final appDir = await getApplicationDocumentsDirectory();
return path.join(appDir.path, 'logs');
}
5.2 常见使用场景
5.2.1 网络请求日志
class NetworkLogger {
static final _logger = DSLogger();
static void logRequest(
String url,
String method,
Map<String, dynamic>? headers,
dynamic body,
) {
_logger.d('API Request',
tag: 'Network',
error: {
'url': url,
'method': method,
'headers': headers,
'body': body,
},
);
}
static void logResponse(
String url,
int statusCode,
dynamic response,
Duration duration,
) {
_logger.d('API Response',
tag: 'Network',
error: {
'url': url,
'statusCode': statusCode,
'duration': '${duration.inMilliseconds}ms',
'response': response,
},
);
}
}
5.2.2 错误边界处理
class LoggingErrorBoundary extends StatelessWidget {
final Widget child;
final DSLogger _logger = DSLogger();
LoggingErrorBoundary({required this.child});
@override
Widget build(BuildContext context) {
return ErrorWidget.builder = (FlutterErrorDetails details) {
_logger.e('UI Render Error',
error: details.exception,
stackTrace: details.stack,
);
return kDebugMode
? ErrorWidget(details.exception)
: Container(
alignment: Alignment.center,
child: Text('Something went wrong'),
);
};
}
}
6. 最佳实践
6.1 日志分级使用
级别 | 使用场景 | 示例 |
---|
VERBOSE | 详细的调试信息 | 界面生命周期、细节状态变化 |
DEBUG | 开发调试信息 | API 请求响应、关键流程节点 |
INFO | 重要业务信息 | 用户操作、业务流程完成 |
WARNING | 潜在问题警告 | 非致命错误、性能警告 |
ERROR | 错误信息 | 异常捕获、业务错误 |
6.2 性能优化建议
graph TD
A[性能优化] --> B1[合理分级]
A --> B2[批量写入]
A --> B3[及时清理]
B1 --> C1[生产环境使用INFO级别]
B2 --> C2[使用缓冲区]
B3 --> C3[定期清理旧日志]
7. 使用指南
7.1 基础用法
void main() async {
await DSLogger.init(
LogConfig(
minLevel: LogLevel.debug,
logDir: 'app_logs',
maxFileSize: 10 * 1024 * 1024,
maxFiles: 5,
),
);
final logger = DSLogger();
logger.d('Debug message');
logger.i('Info message', tag: 'Network');
final longText = 'Very long text ' * 1000;
logger.d('Long text content',
tag: 'Content',
error: LogContent.text(longText, maxLength: 1000),
);
final base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...';
logger.i('Image uploaded',
tag: 'Upload',
error: LogContent.base64(
base64Image,
preview: true,
type: 'image',
),
);
final jsonData = {
'user': {'id': 1, 'name': 'Test'},
'data': List.generate(100, (i) => 'item $i'),
};
logger.d('API Response',
tag: 'Network',
error: LogContent.json(jsonData),
);
final bytes = Uint8List.fromList([0x68, 0x65, 0x6C, 0x6C, 0x6F]);
logger.d('Binary data',
tag: 'Data',
error: LogContent.binary(bytes, format: BinaryFormat.hex),
);
}
7.2 数据处理工具
class LogContent {
static String text(String content, {
int maxLength = 1000,
bool showEllipsis = true,
}) {
if (content.length <= maxLength) return content;
return '${content.substring(0, maxLength)}'
'${showEllipsis ? '...(${content.length - maxLength} more chars)' : ''}';
}
static String base64(String content, {
bool preview = true,
String? type,
int previewLength = 100,
}) {
final typeInfo = type != null ? '[$type] ' : '';
final size = content.length;
if (!preview) return '$typeInfo(Base64 size: $size)\n$content';
return '$typeInfo(Base64 size: $size)\n'
'${content.substring(0, math.min(previewLength, content.length))}'
'${content.length > previewLength ? '...' : ''}';
}
static String json(dynamic content) {
const encoder = JsonEncoder.withIndent(' ');
try {
return encoder.convert(content);
} catch (e) {
return content.toString();
}
}
static String binary(Uint8List bytes, {
BinaryFormat format = BinaryFormat.hex,
int maxLength = 100,
}) {
switch (format) {
case BinaryFormat.hex:
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0'))
.join(' ');
case BinaryFormat.base64:
return base64Encode(bytes);
default:
return bytes.toString();
}
}
}
8.END