From b8d0417d0f490d6d182f75a36fbc247672090b61 Mon Sep 17 00:00:00 2001 From: Rust Date: Sun, 2 Nov 2025 17:10:52 +0800 Subject: [PATCH] update (cherry picked from commit 52f368c3417efe0e88052bb811c8fc8d8fb7e8b9) --- lib/app/utils/kr_init_log_collector.dart | 308 +++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 lib/app/utils/kr_init_log_collector.dart diff --git a/lib/app/utils/kr_init_log_collector.dart b/lib/app/utils/kr_init_log_collector.dart new file mode 100644 index 0000000..a9f9e37 --- /dev/null +++ b/lib/app/utils/kr_init_log_collector.dart @@ -0,0 +1,308 @@ +import 'dart:io'; +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:intl/intl.dart'; +import '../common/app_config.dart'; + +/// 初始化日志收集器 +/// 用于收集应用启动和初始化过程中的所有日志,方便问题诊断 +/// +/// 使用说明: +/// 1. 通过 AppConfig.enableInitLogCollection 全局开关控制是否收集日志 +/// 2. 日志文件保存在:{应用文档目录}/init_logs/init_log_yyyyMMdd_HHmmss.txt +/// 3. 自动保留最近5个日志文件,旧文件会被自动清理 +class KRInitLogCollector { + static final KRInitLogCollector _instance = KRInitLogCollector._internal(); + factory KRInitLogCollector() => _instance; + KRInitLogCollector._internal(); + + /// 日志文件 + File? _logFile; + + /// 🔧 终极修复:使用 RandomAccessFile 进行真正的同步写入 + RandomAccessFile? _logFileHandle; + + /// 日志缓冲区(用于在文件创建前暂存日志) + final List _logBuffer = []; + + /// 是否已初始化 + bool _isInitialized = false; + + /// 初始化开始时间 + DateTime? _initStartTime; + + /// 日志文件路径 + String? _logFilePath; + + /// 🔧 关键:检查是否启用日志收集 + bool get _isEnabled => AppConfig.enableInitLogCollection; + + /// 初始化日志收集器 + Future initialize() async { + // 🔧 检查全局开关 + if (!_isEnabled) { + if (kDebugMode) { + print('📝 初始化日志收集已关闭(AppConfig.enableInitLogCollection = false)'); + } + return; + } + + if (_isInitialized) return; + + try { + _initStartTime = DateTime.now(); + + // 获取应用文档目录 + final directory = await getApplicationDocumentsDirectory(); + final logDir = Directory('${directory.path}/init_logs'); + + // 创建日志目录 + if (!await logDir.exists()) { + await logDir.create(recursive: true); + } + + // 创建日志文件(使用时间戳命名) + final timestamp = DateFormat('yyyyMMdd_HHmmss').format(_initStartTime!); + _logFile = File('${logDir.path}/init_log_$timestamp.txt'); + _logFilePath = _logFile!.path; + + // 🔧 终极修复:使用 RandomAccessFile 进行真正的同步写入 + _logFileHandle = await _logFile!.open(mode: FileMode.write); + + // 写入日志头部 + _writeHeader(); + + // 将缓冲区中的日志写入文件 + if (_logBuffer.isNotEmpty) { + for (var log in _logBuffer) { + _writeToFile(log); + } + _logBuffer.clear(); + } + + _isInitialized = true; + + // 打印日志文件路径到控制台,方便查看 + if (kDebugMode) { + print('📝 初始化日志文件已创建: $_logFilePath'); + } + + // 清理旧日志文件(保留最近5个) + await _cleanOldLogs(logDir); + + } catch (e) { + if (kDebugMode) { + print('❌ 初始化日志收集器失败: $e'); + } + } + } + + /// 写入日志头部信息 + void _writeHeader() { + final header = ''' +═══════════════════════════════════════════════════════════ + 应用初始化日志 +═══════════════════════════════════════════════════════════ +开始时间: ${DateFormat('yyyy-MM-dd HH:mm:ss').format(_initStartTime!)} +设备信息: ${Platform.operatingSystem} ${Platform.operatingSystemVersion} +Flutter版本: ${Platform.version} +═══════════════════════════════════════════════════════════ + +'''; + _writeToFile(header); + } + + /// 记录日志 + void log(String message, {String tag = 'INIT'}) { + // 🔧 检查全局开关 + if (!_isEnabled) return; + + final timestamp = DateTime.now(); + final elapsed = _initStartTime != null + ? timestamp.difference(_initStartTime!).inMilliseconds + : 0; + + final logLine = '[${DateFormat('HH:mm:ss.SSS').format(timestamp)}] ' + '[+${elapsed}ms] ' + '[$tag] ' + '$message'; + + // 同时输出到控制台(DEBUG模式) + if (kDebugMode) { + print(logLine); + } + + // 🔧 终极修复:检查 RandomAccessFile 是否可用 + if (_isInitialized && _logFileHandle != null) { + _writeToFile('$logLine\n'); + } else { + // 文件未创建前,暂存到缓冲区 + _logBuffer.add('$logLine\n'); + } + } + + /// 记录分隔线 + void logSeparator() { + log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: ''); + } + + /// 记录错误 + void logError(String message, {String tag = 'ERROR', Object? error, StackTrace? stackTrace}) { + log('❌ $message', tag: tag); + if (error != null) { + log(' 错误详情: $error', tag: tag); + } + if (stackTrace != null) { + log(' 堆栈跟踪: $stackTrace', tag: tag); + } + } + + /// 记录警告 + void logWarning(String message, {String tag = 'WARN'}) { + log('⚠️ $message', tag: tag); + } + + /// 记录成功 + void logSuccess(String message, {String tag = 'SUCCESS'}) { + log('✅ $message', tag: tag); + } + + /// 记录阶段开始 + void logPhaseStart(String phase) { + logSeparator(); + log('🎬 开始阶段: $phase', tag: 'PHASE'); + logSeparator(); + } + + /// 记录阶段完成 + void logPhaseEnd(String phase, {bool success = true}) { + final icon = success ? '✅' : '❌'; + log('$icon 完成阶段: $phase', tag: 'PHASE'); + logSeparator(); + } + + /// 🔧 终极修复:使用 RandomAccessFile 进行真正的同步写入 + void _writeToFile(String content) { + try { + if (_logFileHandle != null) { + // 使用 writeStringSync 进行同步写入 + _logFileHandle!.writeStringSync(content); + // 立即刷新到磁盘,确保数据持久化 + _logFileHandle!.flushSync(); + } + } catch (e, stackTrace) { + if (kDebugMode) { + print('❌ 写入日志文件失败: $e'); + print('📚 堆栈跟踪: $stackTrace'); + } + // 尝试输出到控制台作为备份 + if (kDebugMode) { + print('[日志备份] $content'); + } + } + } + + /// 清理旧日志文件(保留最近N个) + Future _cleanOldLogs(Directory logDir, {int keepCount = 5}) async { + try { + final files = logDir.listSync() + .whereType() + .where((f) => f.path.contains('init_log_')) + .toList(); + + if (files.length <= keepCount) return; + + // 按修改时间排序 + files.sort((a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync())); + + // 删除多余的旧文件 + for (var i = keepCount; i < files.length; i++) { + await files[i].delete(); + if (kDebugMode) { + print('🧹 已删除旧日志文件: ${files[i].path}'); + } + } + } catch (e) { + if (kDebugMode) { + print('❌ 清理旧日志文件失败: $e'); + } + } + } + + /// 记录完成并写入汇总信息 + Future finalize() async { + if (!_isInitialized || _logFileHandle == null) return; + + try { + final endTime = DateTime.now(); + final totalDuration = endTime.difference(_initStartTime!); + + final footer = ''' + +═══════════════════════════════════════════════════════════ + 初始化完成 +═══════════════════════════════════════════════════════════ +结束时间: ${DateFormat('yyyy-MM-dd HH:mm:ss').format(endTime)} +总耗时: ${totalDuration.inMilliseconds}ms (${totalDuration.inSeconds}秒) +═══════════════════════════════════════════════════════════ +'''; + + _writeToFile(footer); + + // 🔧 终极修复:使用同步方法关闭文件,确保所有数据都写入 + _logFileHandle!.flushSync(); + await _logFileHandle!.close(); + _logFileHandle = null; + + if (kDebugMode) { + print('📝 初始化日志已完成: $_logFilePath'); + print('📊 总耗时: ${totalDuration.inMilliseconds}ms'); + } + } catch (e, stackTrace) { + if (kDebugMode) { + print('❌ 完成日志记录失败: $e'); + print('📚 堆栈跟踪: $stackTrace'); + } + } + } + + /// 获取日志文件路径(用于分享给用户) + String? getLogFilePath() => _logFilePath; + + /// 获取所有日志文件列表 + Future> getAllLogFiles() async { + try { + final directory = await getApplicationDocumentsDirectory(); + final logDir = Directory('${directory.path}/init_logs'); + + if (!await logDir.exists()) return []; + + return logDir.listSync() + .whereType() + .where((f) => f.path.contains('init_log_')) + .toList() + ..sort((a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync())); + } catch (e) { + if (kDebugMode) { + print('❌ 获取日志文件列表失败: $e'); + } + return []; + } + } + + /// 获取最新的日志文件 + Future getLatestLogFile() async { + final files = await getAllLogFiles(); + return files.isNotEmpty ? files.first : null; + } + + /// 读取日志文件内容 + Future readLogFile(File file) async { + try { + return await file.readAsString(); + } catch (e) { + return '读取日志文件失败: $e'; + } + } +}