Skip to content

Commit 318d6fd

Browse files
committed
feat: optimize sync logic & implement debug logging system
Sync Optimization (同步核心优化): * fix(sync): 修复 `_applyRemoteData` 导致的循环同步 (为 `BookMarkService` 新增 `skipSync` 参数) * perf(sync): 优化后台同步去抖动延迟 (Debounce: 1s -> 500ms) * feat(retry): 实现同步队列智能合并与自动重试机制 (支持指数退避 + 错误分类) * logic: 引入 `_pendingSyncCount` 准确追踪挂起请求状态 Debug Logging System (调试日志系统): * feat(viewer): 新增 `LogViewerPage` 及配套 UI 组件 (`LogCard`, `DiagnosisCard`) * feat(core): 实现 `LogBufferService` 用于全局日志捕获与缓冲 * feat(diagnosis): 集成智能诊断引擎 (`LogDiagnostics`),支持识别同步超时、鉴权失败等常见错误 * ui: 支持多维度过滤 (Level/Module/Search) 及日志流自动跟随 (Auto-follow) Refactor & Performance (重构与性能): * perf(lifecycle): 移除 `HomePage` 与 `HistoryPage` 中冗余的生命周期刷新 (didChangeDependencies/AppLifecycleState) * config: 调整默认日志级别 (ALL -> INFO) 以屏蔽框架层冗余输出 * chore: 在 `SettingsPage` 集成调试入口
1 parent 81330d1 commit 318d6fd

15 files changed

Lines changed: 1180 additions & 44 deletions
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import 'package:logging/logging.dart';
2+
import 'package:novella/core/logging/models/log_entry.dart';
3+
4+
/// 日志缓冲服务
5+
/// 捕获所有应用日志到内存缓冲区
6+
class LogBufferService {
7+
static final List<LogEntry> _buffer = [];
8+
static const int _maxSize = 1000;
9+
static bool _initialized = false;
10+
11+
static void init() {
12+
if (_initialized) return;
13+
14+
// 设置日志级别为 INFO,过滤掉过于详细的调试日志 (FINEST/FINER/FINE)
15+
Logger.root.level = Level.INFO;
16+
Logger.root.onRecord.listen((record) {
17+
final entry = LogEntry.fromRecord(record);
18+
_buffer.add(entry);
19+
20+
// 保持缓冲区大小限制
21+
if (_buffer.length > _maxSize) {
22+
_buffer.removeAt(0);
23+
}
24+
});
25+
26+
_initialized = true;
27+
}
28+
29+
/// 获取所有日志
30+
static List<LogEntry> getLogs({Level? minLevel, String? loggerName}) {
31+
var logs = List<LogEntry>.from(_buffer);
32+
33+
// 按日志级别过滤
34+
if (minLevel != null) {
35+
logs = logs.where((e) => e.level >= minLevel).toList();
36+
}
37+
38+
// 按模块名过滤
39+
if (loggerName != null) {
40+
logs = logs.where((e) => e.loggerName == loggerName).toList();
41+
}
42+
43+
return logs;
44+
}
45+
46+
/// 清空日志缓冲区
47+
static void clear() {
48+
_buffer.clear();
49+
}
50+
51+
/// 获取所有模块名
52+
static Set<String> getLoggerNames() {
53+
return _buffer.map((e) => e.loggerName).toSet();
54+
}
55+
56+
/// 获取日志统计信息
57+
static Map<Level, int> getStatistics() {
58+
final stats = <Level, int>{};
59+
for (final entry in _buffer) {
60+
stats[entry.level] = (stats[entry.level] ?? 0) + 1;
61+
}
62+
return stats;
63+
}
64+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:novella/core/logging/models/log_entry.dart';
3+
import 'package:novella/core/logging/models/log_diagnosis.dart';
4+
5+
/// 日志诊断引擎
6+
/// 根据日志内容智能识别问题并提供解决方案
7+
class LogDiagnostics {
8+
/// 诊断日志条目
9+
static LogDiagnosis? diagnose(LogEntry entry) {
10+
final msg = entry.message.toLowerCase();
11+
final logger = entry.loggerName;
12+
13+
// ===== 同步相关问题 =====
14+
if (logger == 'SyncManager') {
15+
// 网络超时
16+
if (msg.contains('timeout') || msg.contains('超时')) {
17+
return const LogDiagnosis(
18+
problemId: 'sync_timeout',
19+
icon: Icons.wifi_off,
20+
title: '同步超时',
21+
description: 'GitHub 连接超时,可能是网络不稳定',
22+
solution: '检查网络连接;如果使用代理,请确保配置正确;系统会自动重试',
23+
severity: DiagnosisSeverity.warning,
24+
);
25+
}
26+
27+
// 密码错误
28+
if (msg.contains('密码') || msg.contains('解密失败')) {
29+
return const LogDiagnosis(
30+
problemId: 'sync_password_error',
31+
icon: Icons.lock_outline,
32+
title: '同步密码错误',
33+
description: '无法解密云端数据,可能是密码不正确',
34+
solution: '前往【设置 → 云同步】重新设置同步密码',
35+
severity: DiagnosisSeverity.error,
36+
);
37+
}
38+
39+
// Token 过期
40+
if (msg.contains('unauthorized') || msg.contains('token')) {
41+
return const LogDiagnosis(
42+
problemId: 'sync_auth_expired',
43+
icon: Icons.key_off,
44+
title: 'GitHub 授权失效',
45+
description: '访问令牌已过期或被撤销',
46+
solution: '前往【设置 → 云同步】点击"断开连接"后重新授权',
47+
severity: DiagnosisSeverity.error,
48+
);
49+
}
50+
51+
// 重试中
52+
if (msg.contains('retrying') ||
53+
msg.contains('retry') ||
54+
msg.contains('重试')) {
55+
return const LogDiagnosis(
56+
problemId: 'sync_retrying',
57+
icon: Icons.refresh,
58+
title: '同步重试中',
59+
description: '检测到临时故障,系统正在自动重试',
60+
solution: '无需操作,系统会自动恢复;如果持续失败请检查网络',
61+
severity: DiagnosisSeverity.info,
62+
);
63+
}
64+
65+
// 同步成功
66+
if (msg.contains('completed successfully') || msg.contains('同步完成')) {
67+
return const LogDiagnosis(
68+
problemId: 'sync_success',
69+
icon: Icons.cloud_done,
70+
title: '同步成功',
71+
description: '数据已成功同步到 GitHub Gist',
72+
solution: '数据已备份,可放心使用',
73+
severity: DiagnosisSeverity.info,
74+
);
75+
}
76+
}
77+
78+
// ===== Gist 服务问题 =====
79+
if (logger == 'GistSyncService') {
80+
// Gist 不存在
81+
if (msg.contains('404') || msg.contains('not found')) {
82+
return const LogDiagnosis(
83+
problemId: 'gist_not_found',
84+
icon: Icons.cloud_off,
85+
title: '云端数据丢失',
86+
description: 'GitHub Gist 文件未找到或已被删除',
87+
solution: '下次同步会自动创建新的 Gist;建议手动触发一次同步',
88+
severity: DiagnosisSeverity.warning,
89+
);
90+
}
91+
92+
// Gist 冲突
93+
if (msg.contains('409') || msg.contains('conflict')) {
94+
return const LogDiagnosis(
95+
problemId: 'gist_conflict',
96+
icon: Icons.merge_type,
97+
title: '同步冲突',
98+
description: '多设备同时修改导致冲突',
99+
solution: '系统会自动重试并合并数据;建议稍后检查数据一致性',
100+
severity: DiagnosisSeverity.warning,
101+
);
102+
}
103+
104+
// Device Flow 成功
105+
if (msg.contains('device code received') ||
106+
msg.contains('access token obtained')) {
107+
return const LogDiagnosis(
108+
problemId: 'auth_success',
109+
icon: Icons.verified_user,
110+
title: 'GitHub 授权成功',
111+
description: '已成功连接到 GitHub',
112+
solution: '现在可以使用云同步功能',
113+
severity: DiagnosisSeverity.info,
114+
);
115+
}
116+
}
117+
118+
// ===== Rust FFI 问题 =====
119+
if (msg.contains('rustlib') || msg.contains('ffi') || logger == 'Flutter') {
120+
if (msg.contains('failed') || msg.contains('error')) {
121+
return const LogDiagnosis(
122+
problemId: 'rust_ffi_error',
123+
icon: Icons.memory,
124+
title: 'FFI 初始化失败',
125+
description: '繁简转换引擎加载失败',
126+
solution: '繁简转换功能不可用;请检查应用是否完整安装',
127+
severity: DiagnosisSeverity.error,
128+
);
129+
}
130+
}
131+
132+
// ===== 网络错误 =====
133+
if (msg.contains('socketexception') ||
134+
msg.contains('connection refused') ||
135+
msg.contains('network error')) {
136+
return const LogDiagnosis(
137+
problemId: 'network_error',
138+
icon: Icons.signal_wifi_statusbar_connected_no_internet_4,
139+
title: '网络连接失败',
140+
description: '无法连接到服务器',
141+
solution: '检查网络连接和防火墙设置',
142+
severity: DiagnosisSeverity.error,
143+
);
144+
}
145+
146+
// ===== HTTP 错误码 =====
147+
if (msg.contains('403') || msg.contains('forbidden')) {
148+
return const LogDiagnosis(
149+
problemId: 'http_forbidden',
150+
icon: Icons.block,
151+
title: '访问被拒绝',
152+
description: '服务器拒绝了请求',
153+
solution: '可能是权限不足或请求过于频繁;请稍后重试',
154+
severity: DiagnosisSeverity.error,
155+
);
156+
}
157+
158+
if (msg.contains('500') || msg.contains('internal server error')) {
159+
return const LogDiagnosis(
160+
problemId: 'server_error',
161+
icon: Icons.dns_outlined,
162+
title: '服务器错误',
163+
description: '服务器内部错误',
164+
solution: '这是服务端的问题,请稍后重试',
165+
severity: DiagnosisSeverity.error,
166+
);
167+
}
168+
169+
return null; // 无匹配规则
170+
}
171+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import 'package:flutter/material.dart';
2+
3+
/// 诊断严重程度
4+
enum DiagnosisSeverity { info, warning, error, critical }
5+
6+
/// 日志诊断结果
7+
class LogDiagnosis {
8+
final String problemId;
9+
final IconData icon;
10+
final String title;
11+
final String description;
12+
final String solution;
13+
final DiagnosisSeverity severity;
14+
15+
const LogDiagnosis({
16+
required this.problemId,
17+
required this.icon,
18+
required this.title,
19+
required this.description,
20+
required this.solution,
21+
required this.severity,
22+
});
23+
24+
/// 获取严重程度对应的颜色
25+
Color getColor(ColorScheme colorScheme) {
26+
switch (severity) {
27+
case DiagnosisSeverity.critical:
28+
case DiagnosisSeverity.error:
29+
return colorScheme.error;
30+
case DiagnosisSeverity.warning:
31+
return const Color(0xFFFFA726); // Material Orange 400
32+
case DiagnosisSeverity.info:
33+
return colorScheme.primary;
34+
}
35+
}
36+
37+
/// 获取严重程度对应的图标
38+
IconData getSeverityIcon() {
39+
switch (severity) {
40+
case DiagnosisSeverity.critical:
41+
return Icons.dangerous;
42+
case DiagnosisSeverity.error:
43+
return Icons.error_outline;
44+
case DiagnosisSeverity.warning:
45+
return Icons.warning_amber;
46+
case DiagnosisSeverity.info:
47+
return Icons.info_outline;
48+
}
49+
}
50+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import 'package:logging/logging.dart';
2+
import 'package:novella/core/logging/models/log_diagnosis.dart';
3+
import 'package:novella/core/logging/log_diagnostics.dart';
4+
5+
/// 日志条目模型
6+
class LogEntry {
7+
final Level level;
8+
final String loggerName;
9+
final String message;
10+
final DateTime time;
11+
final Object? error;
12+
final StackTrace? stackTrace;
13+
14+
LogEntry({
15+
required this.level,
16+
required this.loggerName,
17+
required this.message,
18+
required this.time,
19+
this.error,
20+
this.stackTrace,
21+
});
22+
23+
/// 从 LogRecord 创建
24+
factory LogEntry.fromRecord(LogRecord record) {
25+
return LogEntry(
26+
level: record.level,
27+
loggerName: record.loggerName,
28+
message: record.message,
29+
time: record.time,
30+
error: record.error,
31+
stackTrace: record.stackTrace,
32+
);
33+
}
34+
35+
/// 获取智能诊断结果
36+
LogDiagnosis? get diagnosis => LogDiagnostics.diagnose(this);
37+
38+
/// 获取格式化的时间字符串
39+
String get formattedTime {
40+
final h = time.hour.toString().padLeft(2, '0');
41+
final m = time.minute.toString().padLeft(2, '0');
42+
final s = time.second.toString().padLeft(2, '0');
43+
return '$h:$m:$s';
44+
}
45+
}

0 commit comments

Comments
 (0)