Some checks failed
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (iOS/tvOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 构建 iOS (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
(cherry picked from commit 52f368c3417efe0e88052bb811c8fc8d8fb7e8b9)
309 lines
9.6 KiB
Dart
309 lines
9.6 KiB
Dart
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<String> _logBuffer = [];
|
||
|
||
/// 是否已初始化
|
||
bool _isInitialized = false;
|
||
|
||
/// 初始化开始时间
|
||
DateTime? _initStartTime;
|
||
|
||
/// 日志文件路径
|
||
String? _logFilePath;
|
||
|
||
/// 🔧 关键:检查是否启用日志收集
|
||
bool get _isEnabled => AppConfig.enableInitLogCollection;
|
||
|
||
/// 初始化日志收集器
|
||
Future<void> 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<void> _cleanOldLogs(Directory logDir, {int keepCount = 5}) async {
|
||
try {
|
||
final files = logDir.listSync()
|
||
.whereType<File>()
|
||
.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<void> 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<List<File>> getAllLogFiles() async {
|
||
try {
|
||
final directory = await getApplicationDocumentsDirectory();
|
||
final logDir = Directory('${directory.path}/init_logs');
|
||
|
||
if (!await logDir.exists()) return [];
|
||
|
||
return logDir.listSync()
|
||
.whereType<File>()
|
||
.where((f) => f.path.contains('init_log_'))
|
||
.toList()
|
||
..sort((a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync()));
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ 获取日志文件列表失败: $e');
|
||
}
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/// 获取最新的日志文件
|
||
Future<File?> getLatestLogFile() async {
|
||
final files = await getAllLogFiles();
|
||
return files.isNotEmpty ? files.first : null;
|
||
}
|
||
|
||
/// 读取日志文件内容
|
||
Future<String> readLogFile(File file) async {
|
||
try {
|
||
return await file.readAsString();
|
||
} catch (e) {
|
||
return '读取日志文件失败: $e';
|
||
}
|
||
}
|
||
}
|