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 让调用者知道日志是否成功保存 * 6. 修复日志丢失问题 - 确保所有日志都能被正确保存 */ class Logger { private static instance: Logger; private readonly maxLogs = 2000; // 最多保存2000条日志 private readonly storageKey = '@app_logs'; // 内存队列相关 private memoryQueue: LogEntry[] = []; private readonly queueMaxSize = 10; // 达到10条日志时触发批量写入 private readonly flushInterval = 5000; // 5秒自动刷新一次 private flushTimer: ReturnType | null = null; // 写入锁相关 private isWriting = false; private writePromise: Promise | null = null; private pendingFlushes: Array<() => void> = []; // 等待刷新完成的回调队列 // ID 生成相关 private idCounter = 0; private lastTimestamp = 0; // 重试相关 private readonly maxRetries = 3; private readonly retryDelay = 1000; // 1秒 // 应用退出标志 private isShuttingDown = false; 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 = async () => { this.isShuttingDown = true; if (this.memoryQueue.length > 0) { console.log('[Logger] App exiting, flushing remaining logs...'); try { // 在退出时等待刷新完成 await this.flushQueue(); console.log('[Logger] Logs flushed successfully on exit'); } catch (error) { console.error('[Logger] Failed to flush logs on exit:', error); } } }; process.on('exit', () => { // exit 事件不能异步,只能尝试 if (this.memoryQueue.length > 0) { console.warn('[Logger] Process exiting with unflushed logs'); } }); // 这些信号可以异步处理 process.on('SIGINT', async () => { await cleanup(); process.exit(0); }); process.on('SIGTERM', async () => { await cleanup(); process.exit(0); }); } } /** * 生成唯一 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 { 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 { 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 { 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 { // 如果正在写入,返回当前的写入 Promise if (this.isWriting && this.writePromise) { return this.writePromise; } // 如果队列为空,直接返回 if (this.memoryQueue.length === 0) { return; } // 设置写入锁 this.isWriting = true; // 复制要写入的日志(但不立即清空队列,避免失败时丢失) const logsToWrite = [...this.memoryQueue]; this.writePromise = (async () => { try { // 获取现有日志 const existingLogs = await this.getLogs(); // 合并日志 const allLogs = [...existingLogs, ...logsToWrite]; // 保存到存储 await this.saveLogs(allLogs); // 写入成功后才从队列中移除这些日志 // 使用 filter 而不是直接赋值,因为在写入过程中可能有新日志添加 const writtenIds = new Set(logsToWrite.map(log => log.id)); this.memoryQueue = this.memoryQueue.filter(log => !writtenIds.has(log.id)); console.log(`[Logger] Successfully flushed ${logsToWrite.length} logs to storage, remaining: ${this.memoryQueue.length}`); // 通知等待的刷新请求 this.notifyPendingFlushes(); } catch (error) { console.error('[Logger] Failed to flush queue:', error); // 写入失败时,日志已经在队列中,无需重新添加 // 但要限制队列大小,避免内存溢出 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); } throw error; // 重新抛出错误,让调用者知道刷新失败 } finally { // 释放写入锁 this.isWriting = false; this.writePromise = null; } })(); return this.writePromise; } /** * 通知等待刷新完成的回调 */ private notifyPendingFlushes(): void { const callbacks = [...this.pendingFlushes]; this.pendingFlushes = []; callbacks.forEach(callback => callback()); } /** * 等待当前刷新完成 */ private async waitForFlush(): Promise { if (!this.isWriting) { return; } return new Promise((resolve) => { this.pendingFlushes.push(resolve); }); } /** * 添加日志到队列 * 修复:确保在达到阈值时正确处理刷新,避免日志丢失 */ private async addLog(level: LogEntry['level'], message: string, data?: any): Promise { // 序列化数据 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) { try { // 等待刷新完成,确保日志不会丢失 await this.flushQueue(); } catch (error) { // 刷新失败时,日志仍在队列中,会在下次刷新时重试 console.error('[Logger] Failed to flush queue after size threshold:', error); } } } /** * 公共日志方法 */ async debug(message: string, data?: any): Promise { await this.addLog('DEBUG', message, data); } async info(message: string, data?: any): Promise { await this.addLog('INFO', message, data); } async warn(message: string, data?: any): Promise { await this.addLog('WARN', message, data); } async error(message: string, data?: any): Promise { await this.addLog('ERROR', message, data); } /** * 获取所有日志(包括内存队列中的) */ async getAllLogs(): Promise { // 先刷新队列 try { await this.flushQueue(); } catch (error) { console.warn('[Logger] Failed to flush before getting logs:', error); } // 然后获取存储中的日志 const storedLogs = await this.getLogs(); // 如果还有未刷新的日志(刷新失败的情况),也包含进来 if (this.memoryQueue.length > 0) { return [...storedLogs, ...this.memoryQueue]; } return storedLogs; } /** * 清除所有日志 */ async clearLogs(): Promise { try { // 清空内存队列 this.memoryQueue = []; // 等待当前写入完成 await this.waitForFlush(); // 清除存储 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 { // 获取所有日志(包括未刷新的) const logs = await this.getAllLogs(); return JSON.stringify(logs, null, 2); } /** * 手动刷新日志到存储 */ async flush(): Promise { await this.flushQueue(); } /** * 获取队列状态(用于调试) */ getQueueStatus(): { queueSize: number; isWriting: boolean; isShuttingDown: boolean } { return { queueSize: this.memoryQueue.length, isWriting: this.isWriting, isShuttingDown: this.isShuttingDown }; } /** * 清理资源 */ async destroy(): Promise { // 设置关闭标志 this.isShuttingDown = true; // 停止定时器 if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = null; } // 最后刷新一次,确保所有日志都被保存 if (this.memoryQueue.length > 0) { try { console.log('[Logger] Destroying logger, flushing remaining logs...'); await this.flushQueue(); console.log('[Logger] Logger destroyed successfully'); } catch (error) { console.error('[Logger] Failed to flush logs on destroy:', error); } } } } // 导出全局日志实例和便捷函数 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(), getAllLogs: () => logger.getAllLogs(), clearLogs: () => logger.clearLogs(), exportLogs: () => logger.exportLogs(), destroy: () => logger.destroy(), }; export type { LogEntry };