Files
xianyan/lib/core/services/crash_log_service.dart
Developer 794da27193 refactor: 完成存储层迁移,替换AppKVStore为KvStorage
主要变更:
1. 重构存储层导入路径,将app_kv_store替换为kv_storage
2. 移除AppKVStore初始化代码,统一使用KvStorage
3. 修复壁纸健康检测逻辑,使用最新检查时间判断检测间隔
4. 调整主页头部容器高度与裁剪行为
5. 新增引导页下次显示开关与Riverpod提供者
6. 修复API响应List类型转换崩溃问题
7. 优化部分文件头注释格式
2026-05-24 05:54:14 +08:00

208 lines
5.5 KiB
Dart

/// ============================================================
/// 闲言APP — 崩溃日志服务
/// 创建时间: 2026-05-21
/// 更新时间: 2026-05-21
/// 作用: 持久化存储崩溃/异常日志,支持增删查清空,自动限制数量
/// 上次更新: 初始创建
/// ============================================================
import 'dart:convert';
import 'dart:io';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart';
import '../utils/logger.dart';
class CrashLogEntry {
const CrashLogEntry({
required this.id,
required this.errorId,
required this.time,
required this.error,
required this.stackTrace,
this.deviceInfo,
this.appVersion,
});
final String id;
final String errorId;
final DateTime time;
final String error;
final String stackTrace;
final String? deviceInfo;
final String? appVersion;
Map<String, dynamic> toJson() => {
'id': id,
'errorId': errorId,
'time': time.toIso8601String(),
'error': error,
'stackTrace': stackTrace,
if (deviceInfo != null) 'deviceInfo': deviceInfo,
if (appVersion != null) 'appVersion': appVersion,
};
factory CrashLogEntry.fromJson(Map<String, dynamic> json) => CrashLogEntry(
id: json['id'] as String,
errorId: json['errorId'] as String,
time: DateTime.parse(json['time'] as String),
error: json['error'] as String,
stackTrace: json['stackTrace'] as String,
deviceInfo: json['deviceInfo'] as String?,
appVersion: json['appVersion'] as String?,
);
String toDisplayText() {
final buffer = StringBuffer();
buffer.writeln('[$errorId] ${time.toIso8601String()}');
if (appVersion != null) buffer.writeln('版本: $appVersion');
if (deviceInfo != null) buffer.writeln('设备: $deviceInfo');
buffer.writeln();
buffer.writeln('Error:');
buffer.writeln(error);
buffer.writeln();
buffer.writeln('Stack Trace:');
buffer.writeln(stackTrace);
return buffer.toString();
}
}
class CrashLogService {
CrashLogService._();
static final CrashLogService instance = CrashLogService._();
static const _maxLogs = 100;
static const _fileName = 'crash_logs.json';
List<CrashLogEntry> _logs = [];
List<CrashLogEntry> get logs => List.unmodifiable(_logs);
int get count => _logs.length;
bool _loaded = false;
Future<File> _getFile() async {
final dir = await getApplicationSupportDirectory();
return File('${dir.path}/$_fileName');
}
Future<void> ensureLoaded() async {
if (_loaded) return;
await load();
}
Future<void> load() async {
try {
final file = await _getFile();
if (!await file.exists()) {
_logs = [];
_loaded = true;
return;
}
final content = await file.readAsString();
final list = jsonDecode(content) as List<dynamic>;
_logs = list
.map((e) => CrashLogEntry.fromJson(e as Map<String, dynamic>))
.toList();
_logs.sort((a, b) => b.time.compareTo(a.time));
_loaded = true;
Log.d('CrashLogService: 已加载 ${_logs.length} 条崩溃日志');
} catch (e) {
Log.e('CrashLogService: 加载失败', e);
_logs = [];
_loaded = true;
}
}
Future<void> _save() async {
try {
final file = await _getFile();
final json = jsonEncode(_logs.map((e) => e.toJson()).toList());
await file.writeAsString(json);
} catch (e) {
Log.e('CrashLogService: 保存失败', e);
}
}
Future<CrashLogEntry> addLog({
required String error,
required String stackTrace,
String? deviceInfo,
String? appVersion,
}) async {
await ensureLoaded();
final now = DateTime.now();
final id = now.millisecondsSinceEpoch.toString();
final errorId =
'ERR-${now.millisecondsSinceEpoch.toRadixString(36).toUpperCase()}';
final entry = CrashLogEntry(
id: id,
errorId: errorId,
time: now,
error: error,
stackTrace: stackTrace,
deviceInfo: deviceInfo,
appVersion: appVersion,
);
_logs.insert(0, entry);
if (_logs.length > _maxLogs) {
_logs.removeRange(_maxLogs, _logs.length);
}
await _save();
Log.d('CrashLogService: 新增崩溃日志 [$errorId]');
return entry;
}
Future<void> deleteLog(String id) async {
await ensureLoaded();
_logs.removeWhere((e) => e.id == id);
await _save();
}
Future<void> deleteLogs(List<String> ids) async {
await ensureLoaded();
final idSet = ids.toSet();
_logs.removeWhere((e) => idSet.contains(e.id));
await _save();
}
Future<void> clearAll() async {
_logs.clear();
await _save();
Log.i('CrashLogService: 已清空所有崩溃日志');
}
CrashLogEntry? getById(String id) {
for (final e in _logs) {
if (e.id == id) return e;
}
return null;
}
Future<String> exportAllAsText() async {
await ensureLoaded();
final buffer = StringBuffer();
buffer.writeln('闲言APP 崩溃日志导出');
buffer.writeln('导出时间: ${DateTime.now().toIso8601String()}');
buffer.writeln('条目数量: ${_logs.length}');
buffer.writeln('=' * 60);
for (final entry in _logs) {
buffer.writeln(entry.toDisplayText());
buffer.writeln('-' * 60);
}
return buffer.toString();
}
}
final crashLogServiceProvider = Provider<CrashLogService>((ref) {
final service = CrashLogService.instance;
ref.onDispose(() {});
return service;
});