Files
digital-pilates/utils/logger.ts
richarjiang 2e11f694f8 feat(membership): 实现会员系统和购买流程
- 创建 MembershipModalContext 统一管理会员弹窗
- 优化 MembershipModal 产品套餐展示和购买流程
- 集成 RevenueCat SDK 并初始化内购功能
- 在个人中心添加会员 Banner,引导非会员用户订阅
- 修复日志工具的循环引用问题,确保错误信息正确记录
- 版本更新至 1.0.20

新增了完整的会员购买流程,包括套餐选择、购买确认、购买恢复等功能。会员 Banner 仅对非会员用户展示,已是会员的用户不会看到。同时优化了错误日志记录,避免循环引用导致的序列化失败。
2025-10-24 09:16:04 +08:00

154 lines
4.6 KiB
TypeScript

import AsyncStorage from '@/utils/kvStore';
interface LogEntry {
id: string;
timestamp: number;
level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
message: string;
data?: any;
}
class Logger {
private static instance: Logger;
private readonly maxLogs = 1000; // 最多保存1000条日志
private readonly storageKey = '@app_logs';
static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
private async getLogs(): Promise<LogEntry[]> {
try {
const logsJson = await AsyncStorage.getItem(this.storageKey);
return logsJson ? JSON.parse(logsJson) : [];
} catch (error) {
console.error('Failed to get logs from storage:', error);
return [];
}
}
private async saveLogs(logs: LogEntry[]): Promise<void> {
try {
// 只保留最新的maxLogs条日志
const trimmedLogs = logs.slice(-this.maxLogs);
await AsyncStorage.setItem(this.storageKey, JSON.stringify(trimmedLogs));
} catch (error) {
console.error('Failed to save logs to storage:', error);
}
}
private async addLog(level: LogEntry['level'], message: string, data?: any): Promise<void> {
// 安全地处理数据,避免循环引用
let safeData = data;
if (data && typeof data === 'object') {
try {
// 对于非 ERROR 级别的日志,也进行安全序列化
if (data instanceof Error) {
safeData = {
name: data.name,
message: data.message,
stack: data.stack
};
} else {
// 使用 JSON.stringify 的 replacer 函数处理循环引用
safeData = JSON.parse(JSON.stringify(data, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (value.constructor === Object || Array.isArray(value)) {
return value;
}
// 对于其他对象类型,转换为字符串表示
return value.toString ? value.toString() : '[Object]';
}
return value;
}));
}
} catch (serializeError) {
// 如果序列化失败,只保存基本信息
safeData = {
error: 'Failed to serialize data',
type: typeof data,
toString: data.toString ? data.toString() : 'N/A'
};
}
}
const logEntry: LogEntry = {
id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
timestamp: Date.now(),
level,
message,
data: safeData
};
// 同时在控制台输出 - 使用原生 console 方法避免循环调用
try {
const logMethod = level === 'ERROR' ? console.error :
level === 'WARN' ? console.warn :
level === 'INFO' ? console.info : console.log;
logMethod(`[${level}] ${message}`, safeData);
} catch (consoleError) {
// 如果控制台输出失败,使用最基本的 console.log
console.log(`[${level}] ${message}`, typeof safeData === 'string' ? safeData : 'Object data');
}
try {
const logs = await this.getLogs();
logs.push(logEntry);
await this.saveLogs(logs);
} catch (error) {
// 使用原生 console.error 避免循环调用
console.error('Failed to add log:', 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> {
// addLog 方法已经包含了安全的数据处理逻辑
await this.addLog('ERROR', message, data);
}
async getAllLogs(): Promise<LogEntry[]> {
return await this.getLogs();
}
async clearLogs(): Promise<void> {
try {
await AsyncStorage.removeItem(this.storageKey);
} catch (error) {
console.error('Failed to clear logs:', error);
}
}
async exportLogs(): Promise<string> {
const logs = await this.getLogs();
return JSON.stringify(logs, null, 2);
}
}
// 导出全局日志实例和便捷函数
export const logger = Logger.getInstance();
// 便捷的全局日志函数
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),
};
export type { LogEntry };