Files
digital-pilates/utils/logger.ts
richarjiang 21e57634e0 feat(hrv): 添加心率变异性监控和压力评估功能
- 新增 HRV 监听服务,实时监控心率变异性数据
- 实现 HRV 到压力指数的转换算法和压力等级评估
- 添加智能通知服务,在压力偏高时推送健康建议
- 优化日志系统,修复日志丢失问题并增强刷新机制
- 改进个人页面下拉刷新,支持并行数据加载
- 优化勋章数据缓存策略,减少不必要的网络请求
- 重构应用初始化流程,优化权限服务和健康监听服务的启动顺序
- 移除冗余日志输出,提升应用性能
2025-11-18 14:08:20 +08:00

499 lines
14 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 让调用者知道日志是否成功保存
* 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<typeof setInterval> | null = null;
// 写入锁相关
private isWriting = false;
private writePromise: Promise<void> | 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<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> {
// 如果正在写入,返回当前的写入 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<void> {
if (!this.isWriting) {
return;
}
return new Promise<void>((resolve) => {
this.pendingFlushes.push(resolve);
});
}
/**
* 添加日志到队列
* 修复:确保在达到阈值时正确处理刷新,避免日志丢失
*/
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) {
try {
// 等待刷新完成,确保日志不会丢失
await 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[]> {
// 先刷新队列
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<void> {
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<string> {
// 获取所有日志(包括未刷新的)
const logs = await this.getAllLogs();
return JSON.stringify(logs, null, 2);
}
/**
* 手动刷新日志到存储
*/
async flush(): Promise<void> {
await this.flushQueue();
}
/**
* 获取队列状态(用于调试)
*/
getQueueStatus(): { queueSize: number; isWriting: boolean; isShuttingDown: boolean } {
return {
queueSize: this.memoryQueue.length,
isWriting: this.isWriting,
isShuttingDown: this.isShuttingDown
};
}
/**
* 清理资源
*/
async destroy(): Promise<void> {
// 设置关闭标志
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 };