Files
SyncCalendar/entry/src/main/ets/common/LogUtil.ets
T
laoyang 942f71bfdf 0.0.8 上架收尾:移除同步记录落盘 + 镜像空清单防护 + 前台默认间隔 15 分钟
三处改动(均来自 2026-10-16 真机日志实测):

1) 移除「同步记录」落盘
   · LogUtil 顶部新增 ENABLE_FILE_LOG=false(发行版只输出 console),
     真机排查时改 true 即可恢复落盘,所有调用点无需改动;
   · init() 里顺手 unlink 历史版本遗留的 sync.log / mirror_diag.txt,
     保证升级上来的设备沙箱也干净;
   · SystemCalendarMirror 删掉 dumpDiagnosis 整个方法(不再导出 mirror_diag.txt),
     连带去掉已无引用的 fs 依赖;
   · SettingsPage「清理残留镜像账户」提示语由"见同步日志"改为"见下方"(清单本就在页面上)。

2) 镜像防护:系统日历清单为 0 时跳过创建
   · 依据:2026-10-16 07:55:00 日志 `系统日历账户清单(共 0)` →
     `镜像完成:本=0 … 失败=12`,12 个 bookId 连续 `201 CreateCalendar failed!`。
   · 真因:设备重启后尚未解锁(direct boot),日历数据在加密存储里读不到,
     getAllCalendars() 返回空列表 ⇒ createCalendar 必然失败,还可能留下半创建账户。
   · 正常设备至少有一个本机日历,故"清单为 0"只可能是服务未就绪 ⇒ syncNow()
     在 plan/syncBook 之前直接跳过本轮,等解锁后的下一次同步自然恢复。

3) 前台自动同步默认间隔 1 分钟 → 15 分钟
   · 依据:2026-10-16 17:16–17:20 日志,5 分钟内连跑 5 轮
     "12 本全量比对 + 出站镜像 + 提醒刷新",随后
     `17:20:43 短时任务超时(配额用尽),后台同步将被挂起`
     —— 前台把短时任务配额烧光,反而拖垮了真正的后台同步通道。
   · 设备 preferences 里并无 sync_interval_minutes 键 ⇒ 走的就是这个默认值。
   · AppSettings.getSyncIntervalMinutes 默认值 + Index.startAutoSync 的
     setOrCreate 与 ?? 兜底 + SettingsPage 的 @State 初值,四处一并改为 15。
   · 用户仍可在设置页选 1/5/15/30 分钟 或 1 小时。

验证:assembleHap 与 assembleApp 均 BUILD SUCCESSFUL。
2026-10-16 19:38:21 +08:00

135 lines
4.6 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// entry/src/main/ets/common/LogUtil.ets
// 日志工具:默认**只输出到 console(hilog)**,不落盘。
//
// ⭐ 0.0.8 起:发行版**不再写 sync.log**(原「同步记录」/诊断落盘功能已移除)。
// 需要真机排查同步/提醒问题时,把下面的 ENABLE_FILE_LOG 改成 true 重新编译即可恢复
// 落盘(含 512KB 自动截断保留后半),其余调用点一行都不用动。
// 关闭状态下会在 init 时顺手删掉历史版本遗留的 sync.log 与 mirror_diag.txt,
// 保证升级上来的设备沙箱也是干净的。
import { common } from '@kit.AbilityKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
export class LogUtil {
/** ⭐ 落盘开关:发行版置 false(仅 console);真机排查时改 true 恢复文件日志 */
private static readonly ENABLE_FILE_LOG: boolean = false;
private static dir: string = '';
private static path: string = '';
private static readonly MAX_BYTES: number = 512 * 1024;
static init(context: common.Context): void {
try {
LogUtil.dir = context.filesDir;
LogUtil.path = `${LogUtil.dir}/sync.log`;
} catch (err) {
LogUtil.dir = '';
LogUtil.path = '';
}
if (!LogUtil.ENABLE_FILE_LOG && LogUtil.dir !== '') {
LogUtil.removeLegacyDebugFiles();
}
}
/** 删除历史版本遗留的调试落盘文件(sync.log / mirror_diag.txt) */
private static removeLegacyDebugFiles(): void {
for (const name of ['sync.log', 'mirror_diag.txt']) {
try {
fs.unlinkSync(`${LogUtil.dir}/${name}`);
} catch (err) {
// 文件不存在属正常,忽略
}
}
}
static logFilePath(): string {
return LogUtil.path;
}
/** 写一行日志:console + 文件(发行版不落盘;文件失败静默,不影响业务) */
static write(msg: string): void {
console.info(`SyncLog ${msg}`);
if (!LogUtil.ENABLE_FILE_LOG) {
return;
}
if (LogUtil.path === '') {
return;
}
try {
const d = new Date();
const p = (n: number): string => n < 10 ? '0' + n : String(n);
const ts: string = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}` +
` ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
// 超限截断:保留最后 256KB
try {
const st: fs.Stat = fs.statSync(LogUtil.path);
if (st.size > LogUtil.MAX_BYTES) {
LogUtil.truncate();
}
} catch (err) {
// 文件不存在属正常,直接创建写入
}
const file: fs.File = fs.openSync(LogUtil.path,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.APPEND);
fs.writeSync(file.fd, `[${ts}] ${msg}\n`);
fs.closeSync(file.fd);
} catch (err) {
// 写日志失败不影响业务
}
}
/** 截断:只保留文件末尾 256KB */
private static truncate(): void {
try {
const keep: number = 256 * 1024;
const st: fs.Stat = fs.statSync(LogUtil.path);
const size: number = Math.min(st.size, keep);
const file = fs.openSync(LogUtil.path, fs.OpenMode.READ_ONLY);
const buf: ArrayBuffer = new ArrayBuffer(size);
fs.readSync(file.fd, buf, { offset: st.size - size });
fs.closeSync(file.fd);
const out = fs.openSync(LogUtil.path,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);
fs.writeSync(out.fd, `…(日志过长,已截断保留末尾)\n`);
fs.writeSync(out.fd, new Uint8Array(buf));
fs.closeSync(out.fd);
} catch (err) {
// 截断失败忽略,下次再试
}
}
static readAll(context: common.Context): string {
try {
if (LogUtil.path === '') {
LogUtil.init(context);
}
if (LogUtil.path === '') {
return '(无法确定日志路径)';
}
const st: fs.Stat = fs.statSync(LogUtil.path);
if (st.size === 0) {
return '(日志为空)';
}
const file = fs.openSync(LogUtil.path, fs.OpenMode.READ_ONLY);
const buf: ArrayBuffer = new ArrayBuffer(st.size);
fs.readSync(file.fd, buf);
fs.closeSync(file.fd);
const decoder: util.TextDecoder = util.TextDecoder.create('utf-8');
return decoder.decodeToString(new Uint8Array(buf));
} catch (err) {
return `(读取日志失败: ${(err as BusinessError).message})`;
}
}
static clear(context: common.Context): void {
try {
if (LogUtil.path === '') {
LogUtil.init(context);
}
fs.unlinkSync(LogUtil.path);
} catch (err) {
// 文件不存在等,忽略
}
}
}