log_manager.dart 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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 =
  165. DateTime.now().subtract(const Duration(days: _retentionDays));
  166. final cutoffDateKey = _getDateKey(cutoffDate);
  167. final files = await _logDirectory.list().toList();
  168. for (final file in files) {
  169. if (file is File && file.path.contains('log_')) {
  170. final fileName = file.path.split('/').last;
  171. final dateStr =
  172. fileName.replaceAll('log_', '').replaceAll('.txt', '');
  173. if (dateStr.compareTo(cutoffDateKey) < 0) {
  174. await file.delete();
  175. }
  176. }
  177. }
  178. } catch (e) {
  179. print('Failed to cleanup old logs: $e');
  180. }
  181. }
  182. /// 读取指定日期的日志
  183. Future<String> readLogsForDate(DateTime date) async {
  184. if (!_isInitialized) {
  185. await init();
  186. }
  187. try {
  188. final dateKey = _getDateKey(date);
  189. final logFile = File('${_logDirectory.path}/log_$dateKey.txt');
  190. if (await logFile.exists()) {
  191. return await logFile.readAsString();
  192. }
  193. return '';
  194. } catch (e) {
  195. print('Failed to read logs for date: $e');
  196. return '';
  197. }
  198. }
  199. /// 读取最近几天的日志
  200. Future<String> readRecentLogs(int days) async {
  201. if (!_isInitialized) {
  202. await init();
  203. }
  204. try {
  205. final allLogs = <String>[];
  206. final now = DateTime.now();
  207. for (int i = 0; i < days; i++) {
  208. final date = now.subtract(Duration(days: i));
  209. final dateLogs = await readLogsForDate(date);
  210. if (dateLogs.isNotEmpty) {
  211. allLogs.add(
  212. '=== ${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')} ===\n$dateLogs');
  213. }
  214. }
  215. return allLogs.join('\n');
  216. } catch (e) {
  217. print('Failed to read recent logs: $e');
  218. return '';
  219. }
  220. }
  221. /// 读取所有日志
  222. Future<String> readAllLogs() async {
  223. return await readRecentLogs(_retentionDays);
  224. }
  225. /// 清空所有日志
  226. Future<void> clearAllLogs() async {
  227. if (!_isInitialized) {
  228. await init();
  229. }
  230. try {
  231. // 清空队列
  232. _logQueue.clear();
  233. // 删除所有日志文件
  234. final files = await _logDirectory.list().toList();
  235. for (final file in files) {
  236. if (file is File && file.path.contains('log_')) {
  237. await file.delete();
  238. }
  239. }
  240. } catch (e) {
  241. print('Failed to clear all logs: $e');
  242. }
  243. }
  244. /// 强制刷新所有待写入的日志
  245. Future<void> forceFlush() async {
  246. await _flushLogs();
  247. }
  248. /// 销毁日志管理器
  249. Future<void> dispose() async {
  250. _batchWriteTimer?.cancel();
  251. await _flushLogs();
  252. _isInitialized = false;
  253. }
  254. /// 静态日志方法
  255. static void log(String message, String name) {
  256. if (Configs.debug) {
  257. developer.log(message, name: name);
  258. }
  259. _instance._addToQueue(message, name);
  260. // 添加到简化版开发者工具
  261. IXDeveloperTools.addLog(message, name);
  262. }
  263. }