Files
digital-pilates/utils/logger.ts
richarjiang d74046498d # 分析方案
## 变更内容总结
1. **iOS后台任务系统重构** - 修复后台任务无法自动运行的问题
2. **日志系统优化** - 改进日志记录机制,添加队列和批量写入
3. **文档新增** - 添加后台任务修复总结和测试指南文档
4. **应用启动优化** - 添加后台任务状态检查和恢复逻辑
5. **版本号更新** - Info.plist版本从1.0.23升级到1.0.24

## 提交信息类型判断
- **主要类型**: `fix` - 这是一个重要的bug修复,解决了iOS后台任务无法自动运行的核心问题
- **作用域**: `ios-background` - 专注于iOS后台任务功能
- **影响**: 这个修复对iOS用户的后台功能至关重要

## 提交信息

fix(ios-background): 修复iOS后台任务无法自动运行的问题

主要修复内容:
- 修复BackgroundTaskBridge任务调度逻辑,改用BGAppRefreshTaskRequest
- 添加任务完成后自动重新调度机制,确保任务持续执行
- 优化应用生命周期管理,移除重复的后台任务调度
- 在应用启动时添加后台任务状态检查和恢复功能
- 将默认任务间隔从30分钟优化为15分钟

次要改进:
- 重构日志系统,添加内存队列和批量写入机制,提升性能
- 添加写入锁和重试机制,防止日志数据丢失
- 新增详细的修复总结文档和测试指南

技术细节:
- 使用BGAppRefreshTaskRequest替代BGProcessingTaskRequest
- 实现任务过期自动重新调度
- 添加任务执行状态监控和恢复逻辑
- 优化错误处理和日志输出

影响范围: iOS后台任务调度、通知推送、应用状态管理
2025-11-04 19:14:53 +08:00

439 lines
12 KiB
TypeScript
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.

import AsyncStorage from '@/utils/kvStore';
interface LogEntry {
id: string;
timestamp: number;
level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
message: string;
data?: any;
}
/**
* 改进的日志系统
* 主要改进:
* 1. 内存队列和批量写入机制 - 避免频繁存储操作
* 2. 写入锁机制 - 防止并发写入导致数据丢失
* 3. 改进的错误处理和重试机制
* 4. 优化的 ID 生成 - 确保唯一性
* 5. 写入确认机制 - 返回 Promise 让调用者知道日志是否成功保存
*/
class Logger {
private static instance: Logger;
private readonly maxLogs = 1000; // 最多保存1000条日志
private readonly storageKey = '@app_logs';
// 内存队列相关
private memoryQueue: LogEntry[] = [];
private readonly queueMaxSize = 50; // 达到50条日志时触发批量写入
private readonly flushInterval = 5000; // 5秒自动刷新一次
private flushTimer: ReturnType<typeof setInterval> | null = null;
// 写入锁相关
private isWriting = false;
private writePromise: Promise<void> | null = null;
// ID 生成相关
private idCounter = 0;
private lastTimestamp = 0;
// 重试相关
private readonly maxRetries = 3;
private readonly retryDelay = 1000; // 1秒
static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
constructor() {
// 启动定时刷新机制
this.startFlushTimer();
// 应用退出时确保刷新日志
if (typeof global !== 'undefined') {
// 注册应用退出时的清理函数(如果可用)
this.setupAppExitHandler();
}
}
/**
* 启动定时刷新机制
*/
private startFlushTimer(): void {
if (this.flushTimer) {
clearInterval(this.flushTimer);
}
this.flushTimer = setInterval(() => {
if (this.memoryQueue.length > 0) {
this.flushQueue().catch(error => {
console.error('[Logger] Auto flush failed:', error);
});
}
}, this.flushInterval);
}
/**
* 设置应用退出处理
*/
private setupAppExitHandler(): void {
// 这是一个最佳努力的清理,不是所有场景都能捕获
if (typeof process !== 'undefined' && process.on) {
const cleanup = () => {
if (this.memoryQueue.length > 0) {
// 同步刷新(应用退出时)
this.flushQueueSync();
}
};
process.on('exit', cleanup);
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
}
}
/**
* 生成唯一 ID
*/
private generateId(): string {
const now = Date.now();
// 如果时间戳相同,增加计数器
if (now === this.lastTimestamp) {
this.idCounter++;
} else {
this.lastTimestamp = now;
this.idCounter = 0;
}
// 格式timestamp-counter-random
const random = Math.random().toString(36).substr(2, 5);
return `${now}-${this.idCounter}-${random}`;
}
/**
* 从存储中获取日志(带重试)
*/
private async getLogs(retries = 0): Promise<LogEntry[]> {
try {
const logsJson = await AsyncStorage.getItem(this.storageKey);
return logsJson ? JSON.parse(logsJson) : [];
} catch (error) {
if (retries < this.maxRetries) {
console.warn(`[Logger] Failed to get logs, retrying (${retries + 1}/${this.maxRetries})...`);
await this.delay(this.retryDelay);
return this.getLogs(retries + 1);
}
console.error('[Logger] Failed to get logs after retries:', error);
return [];
}
}
/**
* 保存日志到存储(带重试)
*/
private async saveLogs(logs: LogEntry[], retries = 0): Promise<void> {
try {
// 只保留最新的maxLogs条日志
const trimmedLogs = logs.slice(-this.maxLogs);
await AsyncStorage.setItem(this.storageKey, JSON.stringify(trimmedLogs));
} catch (error) {
if (retries < this.maxRetries) {
console.warn(`[Logger] Failed to save logs, retrying (${retries + 1}/${this.maxRetries})...`);
await this.delay(this.retryDelay);
return this.saveLogs(logs, retries + 1);
}
console.error('[Logger] Failed to save logs after retries:', error);
throw error;
}
}
/**
* 延迟函数
*/
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 安全地序列化数据
*/
private serializeData(data: any): any {
if (!data || typeof data !== 'object') {
return data;
}
try {
// 处理 Error 对象
if (data instanceof Error) {
return {
name: data.name,
message: data.message,
stack: data.stack
};
}
// 处理其他对象 - 使用 replacer 函数处理循环引用
const seen = new WeakSet();
return JSON.parse(JSON.stringify(data, (key, value) => {
if (typeof value === 'object' && value !== null) {
// 检测循环引用
if (seen.has(value)) {
return '[Circular Reference]';
}
seen.add(value);
// 处理特殊对象类型
if (value.constructor === Object || Array.isArray(value)) {
return value;
}
// 对于其他对象类型,转换为字符串表示
return value.toString ? value.toString() : '[Object]';
}
return value;
}));
} catch (serializeError) {
// 如果序列化失败,返回基本信息
return {
error: 'Failed to serialize data',
type: typeof data,
toString: data.toString ? data.toString() : 'N/A'
};
}
}
/**
* 刷新队列到存储(异步,带锁)
*/
private async flushQueue(): Promise<void> {
// 如果正在写入,等待当前写入完成
if (this.isWriting && this.writePromise) {
await this.writePromise;
return;
}
// 如果队列为空,直接返回
if (this.memoryQueue.length === 0) {
return;
}
// 设置写入锁
this.isWriting = true;
// 保存要写入的日志(避免在写入过程中队列被修改)
const logsToWrite = [...this.memoryQueue];
this.memoryQueue = [];
this.writePromise = (async () => {
try {
// 获取现有日志
const existingLogs = await this.getLogs();
// 合并日志
const allLogs = [...existingLogs, ...logsToWrite];
// 保存到存储
await this.saveLogs(allLogs);
console.log(`[Logger] Successfully flushed ${logsToWrite.length} logs to storage`);
} catch (error) {
console.error('[Logger] Failed to flush queue:', error);
// 写入失败,将日志放回队列(保留在内存中)
this.memoryQueue.unshift(...logsToWrite);
// 限制队列大小,避免内存溢出
if (this.memoryQueue.length > this.maxLogs) {
const overflow = this.memoryQueue.length - this.maxLogs;
console.warn(`[Logger] Queue overflow, dropping ${overflow} oldest logs`);
this.memoryQueue = this.memoryQueue.slice(-this.maxLogs);
}
} finally {
// 释放写入锁
this.isWriting = false;
this.writePromise = null;
}
})();
await this.writePromise;
}
/**
* 同步刷新队列(应用退出时使用)
*/
private flushQueueSync(): void {
if (this.memoryQueue.length === 0) {
return;
}
try {
// 注意:这是一个阻塞操作,仅在应用退出时使用
const logsToWrite = [...this.memoryQueue];
this.memoryQueue = [];
// 这里我们无法使用异步操作,只能尝试
console.log(`[Logger] Attempting to flush ${logsToWrite.length} logs synchronously`);
// 实际上在 React Native 中很难做到真正的同步保存
// 这里只是一个最佳努力的尝试
this.flushQueue().catch(error => {
console.error('[Logger] Sync flush failed:', error);
});
} catch (error) {
console.error('[Logger] Failed to flush queue synchronously:', error);
}
}
/**
* 添加日志到队列
*/
private async addLog(level: LogEntry['level'], message: string, data?: any): Promise<void> {
// 序列化数据
const safeData = this.serializeData(data);
// 创建日志条目
const logEntry: LogEntry = {
id: this.generateId(),
timestamp: Date.now(),
level,
message,
data: safeData
};
// 输出到控制台
try {
const logMethod = level === 'ERROR' ? console.error :
level === 'WARN' ? console.warn :
level === 'INFO' ? console.info : console.log;
logMethod(`[${level}] ${message}`, safeData !== undefined ? safeData : '');
} catch (consoleError) {
// 如果控制台输出失败,使用最基本的 console.log
console.log(`[${level}] ${message}`);
}
// 添加到内存队列
this.memoryQueue.push(logEntry);
// 检查是否需要刷新队列
if (this.memoryQueue.length >= this.queueMaxSize) {
// 不等待刷新完成,避免阻塞调用者
this.flushQueue().catch(error => {
console.error('[Logger] Failed to flush queue after size threshold:', error);
});
}
}
/**
* 公共日志方法
*/
async debug(message: string, data?: any): Promise<void> {
await this.addLog('DEBUG', message, data);
}
async info(message: string, data?: any): Promise<void> {
await this.addLog('INFO', message, data);
}
async warn(message: string, data?: any): Promise<void> {
await this.addLog('WARN', message, data);
}
async error(message: string, data?: any): Promise<void> {
await this.addLog('ERROR', message, data);
}
/**
* 获取所有日志(包括内存队列中的)
*/
async getAllLogs(): Promise<LogEntry[]> {
// 先刷新队列
await this.flushQueue();
// 然后获取存储中的日志
return await this.getLogs();
}
/**
* 清除所有日志
*/
async clearLogs(): Promise<void> {
try {
// 清空内存队列
this.memoryQueue = [];
// 清除存储
await AsyncStorage.removeItem(this.storageKey);
console.log('[Logger] All logs cleared successfully');
} catch (error) {
console.error('[Logger] Failed to clear logs:', error);
throw error;
}
}
/**
* 导出日志
*/
async exportLogs(): Promise<string> {
// 先刷新队列
await this.flushQueue();
// 然后获取并导出日志
const logs = await this.getLogs();
return JSON.stringify(logs, null, 2);
}
/**
* 手动刷新日志到存储
*/
async flush(): Promise<void> {
await this.flushQueue();
}
/**
* 获取队列状态(用于调试)
*/
getQueueStatus(): { queueSize: number; isWriting: boolean } {
return {
queueSize: this.memoryQueue.length,
isWriting: this.isWriting
};
}
/**
* 清理资源
*/
destroy(): void {
if (this.flushTimer) {
clearInterval(this.flushTimer);
this.flushTimer = null;
}
// 最后刷新一次
if (this.memoryQueue.length > 0) {
this.flushQueueSync();
}
}
}
// 导出全局日志实例和便捷函数
export const logger = Logger.getInstance();
// 便捷的全局日志函数(返回 Promise 以便调用者可以等待)
export const log = {
debug: (message: string, data?: any) => logger.debug(message, data),
info: (message: string, data?: any) => logger.info(message, data),
warn: (message: string, data?: any) => logger.warn(message, data),
error: (message: string, data?: any) => logger.error(message, data),
// 额外的工具函数
flush: () => logger.flush(),
getQueueStatus: () => logger.getQueueStatus(),
};
export type { LogEntry };