log_manager.dart 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import 'dart:io';
  2. import 'dart:async';
  3. import 'dart:developer' as developer;
  4. import 'dart:collection';
  5. import 'package:path_provider/path_provider.dart';
  6. import '../../app/constants/configs.dart';
  7. import '../developer/ix_developer_tools.dart';
  8. /// 日志条目
  9. class LogEntry {
  10. final DateTime timestamp;
  11. final String message;
  12. final String name;
  13. LogEntry(this.timestamp, this.message, this.name);
  14. @override
  15. String toString() {
  16. return '[${timestamp.toIso8601String()}] $name $message';
  17. }
  18. }
  19. /// 优化的日志管理器
  20. /// 特性:
  21. /// 1. 使用队列批量写入,避免频繁文件操作
  22. /// 2. 按日期分文件存储
  23. /// 3. 自动清理7天前的日志
  24. /// 4. 异步处理,不阻塞主线程
  25. class LogManager {
  26. static final LogManager _instance = LogManager._internal();
  27. // 单例模式
  28. factory LogManager() => _instance;
  29. LogManager._internal();
  30. // 配置常量
  31. static const int _maxLogFileSize = 5 * 1024 * 1024; // 5MB per file
  32. static const int _batchWriteSize = 50; // 批量写入条数
  33. static const Duration _batchWriteInterval = Duration(seconds: 5); // 批量写入间隔
  34. static const int _retentionDays = 7; // 保留天数
  35. static const int _maxQueueSize = 1000; // 队列最大大小
  36. // 状态变量
  37. bool _isInitialized = false;
  38. late Directory _logDirectory;
  39. final Queue<LogEntry> _logQueue = Queue<LogEntry>();
  40. Timer? _batchWriteTimer;
  41. bool _isWriting = false;
  42. /// 初始化日志管理器
  43. Future<void> init() async {
  44. if (_isInitialized) return;
  45. try {
  46. // 获取日志存储目录
  47. final appDir = await getApplicationDocumentsDirectory();
  48. _logDirectory = Directory('${appDir.path}/logs');
  49. // 创建日志目录
  50. if (!await _logDirectory.exists()) {
  51. await _logDirectory.create(recursive: true);
  52. }
  53. // 清理过期日志文件
  54. await _cleanupOldLogs();
  55. // 启动批量写入定时器
  56. _startBatchWriteTimer();
  57. _isInitialized = true;
  58. } catch (e) {
  59. print('Failed to initialize LogManager: $e');
  60. rethrow;
  61. }
  62. }
  63. /// 启动批量写入定时器
  64. void _startBatchWriteTimer() {
  65. _batchWriteTimer?.cancel();
  66. _batchWriteTimer = Timer.periodic(_batchWriteInterval, (_) {
  67. _flushLogs();
  68. });
  69. }
  70. /// 添加日志到队列
  71. void _addToQueue(String message, String name) {
  72. final entry = LogEntry(DateTime.now(), message, name);
  73. _logQueue.add(entry);
  74. // 如果队列满了,立即写入
  75. if (_logQueue.length >= _maxQueueSize) {
  76. _flushLogs();
  77. }
  78. }
  79. /// 批量写入日志
  80. Future<void> _flushLogs() async {
  81. if (_isWriting || _logQueue.isEmpty) return;
  82. _isWriting = true;
  83. try {
  84. final logsToWrite = <LogEntry>[];
  85. // 取出要写入的日志条目
  86. final count = _logQueue.length > _batchWriteSize
  87. ? _batchWriteSize
  88. : _logQueue.length;
  89. for (int i = 0; i < count; i++) {
  90. logsToWrite.add(_logQueue.removeFirst());
  91. }
  92. if (logsToWrite.isNotEmpty) {
  93. await _writeLogsToFile(logsToWrite);
  94. }
  95. } catch (e) {
  96. print('Failed to flush logs: $e');
  97. } finally {
  98. _isWriting = false;
  99. }
  100. }
  101. /// 写入日志到文件
  102. Future<void> _writeLogsToFile(List<LogEntry> logs) async {
  103. try {
  104. // 按日期分组
  105. final logsByDate = <String, List<LogEntry>>{};
  106. for (final log in logs) {
  107. final dateKey = _getDateKey(log.timestamp);
  108. logsByDate.putIfAbsent(dateKey, () => []).add(log);
  109. }
  110. // 写入到对应的日期文件
  111. for (final entry in logsByDate.entries) {
  112. final dateKey = entry.key;
  113. final dateLogs = entry.value;
  114. await _writeToDateFile(dateKey, dateLogs);
  115. }
  116. } catch (e) {
  117. print('Failed to write logs to file: $e');
  118. }
  119. }
  120. /// 写入到指定日期的文件
  121. Future<void> _writeToDateFile(String dateKey, List<LogEntry> logs) async {
  122. try {
  123. final logFile = File('${_logDirectory.path}/log_$dateKey.txt');
  124. // 检查文件大小
  125. if (await logFile.exists()) {
  126. final fileSize = await logFile.length();
  127. if (fileSize > _maxLogFileSize) {
  128. // 文件过大,截断保留最新内容
  129. await _truncateLogFile(logFile);
  130. }
  131. }
  132. // 写入日志
  133. final logContent = '${logs.map((log) => log.toString()).join('\n')}\n';
  134. await logFile.writeAsString(logContent, mode: FileMode.append);
  135. } catch (e) {
  136. print('Failed to write to date file: $e');
  137. }
  138. }
  139. /// 截断日志文件,保留最新内容
  140. Future<void> _truncateLogFile(File logFile) async {
  141. try {
  142. final content = await logFile.readAsString();
  143. final lines = content.split('\n');
  144. // 保留最后一半的内容
  145. final keepLines = (lines.length / 2).floor();
  146. final newContent =
  147. '${lines.sublist(lines.length - keepLines).join('\n')}\n';
  148. await logFile.writeAsString(newContent);
  149. } catch (e) {
  150. print('Failed to truncate log file: $e');
  151. // 如果截断失败,重新创建文件
  152. await logFile.writeAsString('');
  153. }
  154. }
  155. /// 获取日期键值
  156. String _getDateKey(DateTime date) {
  157. return '${date.year.toString().padLeft(4, '0')}'
  158. '${date.month.toString().padLeft(2, '0')}'
  159. '${date.day.toString().padLeft(2, '0')}';
  160. }
  161. /// 清理过期日志文件
  162. Future<void> _cleanupOldLogs() async {
  163. try {
  164. final cutoffDate = DateTime.now().subtract(
  165. const Duration(days: _retentionDays),
  166. );
  167. final cutoffDateKey = _getDateKey(cutoffDate);
  168. final files = await _logDirectory.list().toList();
  169. for (final file in files) {
  170. if (file is File && file.path.contains('log_')) {
  171. final fileName = file.path.split('/').last;
  172. final dateStr = fileName
  173. .replaceAll('log_', '')
  174. .replaceAll('.txt', '');
  175. if (dateStr.compareTo(cutoffDateKey) < 0) {
  176. await file.delete();
  177. }
  178. }
  179. }
  180. } catch (e) {
  181. print('Failed to cleanup old logs: $e');
  182. }
  183. }
  184. /// 读取指定日期的日志
  185. Future<String> readLogsForDate(DateTime date) async {
  186. if (!_isInitialized) {
  187. await init();
  188. }
  189. try {
  190. final dateKey = _getDateKey(date);
  191. final logFile = File('${_logDirectory.path}/log_$dateKey.txt');
  192. if (await logFile.exists()) {
  193. return await logFile.readAsString();
  194. }
  195. return '';
  196. } catch (e) {
  197. print('Failed to read logs for date: $e');
  198. return '';
  199. }
  200. }
  201. /// 读取最近几天的日志
  202. Future<String> readRecentLogs(int days) async {
  203. if (!_isInitialized) {
  204. await init();
  205. }
  206. try {
  207. final allLogs = <String>[];
  208. final now = DateTime.now();
  209. for (int i = 0; i < days; i++) {
  210. final date = now.subtract(Duration(days: i));
  211. final dateLogs = await readLogsForDate(date);
  212. if (dateLogs.isNotEmpty) {
  213. allLogs.add(
  214. '=== ${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')} ===\n$dateLogs',
  215. );
  216. }
  217. }
  218. return allLogs.join('\n');
  219. } catch (e) {
  220. print('Failed to read recent logs: $e');
  221. return '';
  222. }
  223. }
  224. /// 读取所有日志
  225. Future<String> readAllLogs() async {
  226. return await readRecentLogs(_retentionDays);
  227. }
  228. /// 清空所有日志
  229. Future<void> clearAllLogs() async {
  230. if (!_isInitialized) {
  231. await init();
  232. }
  233. try {
  234. // 清空队列
  235. _logQueue.clear();
  236. // 删除所有日志文件
  237. final files = await _logDirectory.list().toList();
  238. for (final file in files) {
  239. if (file is File && file.path.contains('log_')) {
  240. await file.delete();
  241. }
  242. }
  243. } catch (e) {
  244. print('Failed to clear all logs: $e');
  245. }
  246. }
  247. /// 强制刷新所有待写入的日志
  248. Future<void> forceFlush() async {
  249. await _flushLogs();
  250. }
  251. /// 销毁日志管理器
  252. Future<void> dispose() async {
  253. _batchWriteTimer?.cancel();
  254. await _flushLogs();
  255. _isInitialized = false;
  256. }
  257. /// 静态日志方法
  258. static void log(String message, String name) {
  259. if (Configs.debug) {
  260. developer.log(message, name: name);
  261. }
  262. _instance._addToQueue(message, name);
  263. // 添加到简化版开发者工具
  264. IXDeveloperTools.addLog(message, name);
  265. }
  266. }