// utils/logger.ts // 定义原始控制台方法的类型 interface ConsoleMethods { log: typeof console.log; info: typeof console.info; warn: typeof console.warn; error: typeof console.error; debug?: typeof console.debug; trace?: typeof console.trace; table?: typeof console.table; time?: typeof console.time; timeEnd?: typeof console.timeEnd; group?: typeof console.group; groupEnd?: typeof console.groupEnd; groupCollapsed?: typeof console.groupCollapsed; assert?: typeof console.assert; clear?: typeof console.clear; count?: typeof console.count; countReset?: typeof console.countReset; } // 安全地获取控制台方法 const originalConsole: ConsoleMethods = { log: console.log, info: console.info, warn: console.warn, error: console.error, debug: console.debug, trace: console.trace, table: console.table, time: console.time, timeEnd: console.timeEnd, group: console.group, groupEnd: console.groupEnd, groupCollapsed: console.groupCollapsed, assert: console.assert, clear: console.clear, count: console.count, countReset: console.countReset }; // 检查并确保所有方法都存在,不存在的方法用空函数替代 Object.keys(originalConsole).forEach(key => { const methodKey = key as keyof ConsoleMethods; if (!originalConsole[methodKey]) { (originalConsole[methodKey] as any) = () => {}; } }); class Logger { private debugMode: boolean = false; private prefix: string = '[uViewPro]'; private showCallerInfo: boolean = true; /** * 设置调试模式 * @param enabled 是否启用调试模式 */ setDebugMode(enabled: boolean) { this.debugMode = !!enabled; if (this.debugMode) { console.log('[uViewPro] Debug mode enabled'); } else { console.log('[uViewPro] Debug mode disabled'); } return this; } /** * 设置是否显示调用者信息(文件名和行号) * @param show 是否显示调用者信息 */ setShowCallerInfo(show: boolean) { this.showCallerInfo = !!show; return this; } /** * 设置日志前缀 * @param prefix 日志前缀 */ setPrefix(prefix: string) { if (prefix) this.prefix = prefix; return this; } /** * 获取当前调试模式状态 * @returns 当前调试模式状态 */ getDebugMode(): boolean { return this.debugMode; } /** * 从文件路径中提取纯净的文件名(去除查询参数和路径) * @param filePath 文件路径 * @returns 纯净的文件名 */ private extractFileName(filePath: string): string { if (!filePath) return ''; // 去除查询参数(?后面的内容) const withoutQuery = filePath.split('?')[0]; // 使用正斜杠和反斜杠分割路径,取最后一部分 const parts = withoutQuery.split(/[/\\]/); const fileNameWithExt = parts.pop() || ''; return fileNameWithExt; } /** * 获取调用者信息(文件名和行号) * @returns 调用者信息字符串 */ private getCallerInfo(): string { if (!this.showCallerInfo) return ''; try { // 创建一个 Error 对象来获取堆栈跟踪 const error = new Error(); const stack = error.stack; if (!stack) return ''; // 解析堆栈跟踪,找到调用 logger 的文件和行号 const stackLines = stack.split('\n'); // 找到第一个不是 logger.ts 的堆栈行 for (let i = 3; i < stackLines.length; i++) { const line = stackLines[i].trim(); if (line && !line.includes('logger.ts') && !line.includes('Logger.') && !line.includes('at Object.')) { // 提取文件名和行号 const match = line.match(/\(?(.*):(\d+):(\d+)\)?/); if (match) { const filePath = match[1]; const lineNumber = match[2]; const fileName = this.extractFileName(filePath); return `[${fileName}:${lineNumber}]`; } break; } } } catch (e) { // 如果获取调用者信息失败,静默处理 } return ''; } /** * 通用日志输出方法 * @param level 日志级别 * @param args 日志参数 */ private output(level: keyof ConsoleMethods, ...args: any[]): void { if (!this.debugMode || !originalConsole[level]) return; const method = originalConsole[level] as Function; const callerInfo = this.getCallerInfo(); if (this.prefix) { if (callerInfo) { method(`${this.prefix}${callerInfo}`, ...args); } else { method(this.prefix, ...args); } } else { if (callerInfo) { method(callerInfo, ...args); } else { method(...args); } } } /** * 普通日志 * @param args 日志参数 */ log(...args: any[]): void { this.output('log', ...args); } /** * 信息日志 * @param args 日志参数 */ info(...args: any[]): void { this.output('info', ...args); } /** * 警告日志 * @param args 日志参数 */ warn(...args: any[]): void { this.output('warn', ...args); } /** * 错误日志 * @param args 日志参数 */ error(...args: any[]): void { this.output('error', ...args); } /** * 调试日志 * @param args 日志参数 */ debug(...args: any[]): void { if (!originalConsole.debug) return; this.output('debug', ...args); } /** * 堆栈跟踪 * @param args 日志参数 */ trace(...args: any[]): void { if (!originalConsole.trace) return; this.output('trace', ...args); } /** * 表格输出 * @param data 表格数据 * @param columns 列名(可选) */ table(data: any, columns?: string[]): void { if (!this.debugMode || !originalConsole.table) return; if (this.prefix) { originalConsole.log!(this.prefix); } originalConsole.table!(data, columns); } /** * 开始计时 * @param label 计时器标签 */ time(label: string): void { if (!this.debugMode || !originalConsole.time) return; const fullLabel = this.prefix ? `${this.prefix} ${label}` : label; originalConsole.time!(fullLabel); } /** * 结束计时 * @param label 计时器标签 */ timeEnd(label: string): void { if (!this.debugMode || !originalConsole.timeEnd) return; const fullLabel = this.prefix ? `${this.prefix} ${label}` : label; originalConsole.timeEnd!(fullLabel); } /** * 分组日志 * @param label 分组标签 */ group(label: string): void { if (!this.debugMode || !originalConsole.group) return; const fullLabel = this.prefix ? `${this.prefix} ${label}` : label; originalConsole.group!(fullLabel); } /** * 结束分组 */ groupEnd(): void { if (!this.debugMode || !originalConsole.groupEnd) return; originalConsole.groupEnd!(); } /** * 分组日志(默认折叠) * @param label 分组标签 */ groupCollapsed(label: string): void { if (!this.debugMode || !originalConsole.groupCollapsed) return; const fullLabel = this.prefix ? `${this.prefix} ${label}` : label; originalConsole.groupCollapsed!(fullLabel); } /** * 断言 * @param condition 条件 * @param message 错误消息 */ assert(condition: boolean, ...message: any[]): void { if (!this.debugMode || !originalConsole.assert) return; if (this.prefix) { originalConsole.assert!(condition, this.prefix, ...message); } else { originalConsole.assert!(condition, ...message); } } /** * 清空控制台 */ clear(): void { if (!this.debugMode || !originalConsole.clear) return; originalConsole.clear!(); } /** * 计数器 * @param label 计数器标签 */ count(label?: string): void { if (!this.debugMode || !originalConsole.count) return; const fullLabel = this.prefix && label ? `${this.prefix} ${label}` : label || this.prefix; originalConsole.count!(fullLabel); } /** * 重置计数器 * @param label 计数器标签 */ countReset(label?: string): void { if (!this.debugMode || !originalConsole.countReset) return; const fullLabel = this.prefix && label ? `${this.prefix} ${label}` : label || this.prefix; originalConsole.countReset!(fullLabel); } /** * 带样式的日志 * @param style CSS样式 * @param message 消息内容 */ styled(style: string, message: string): void { if (!this.debugMode) return; const callerInfo = this.getCallerInfo(); const fullMessage = callerInfo ? `${message} ${callerInfo}` : message; if (this.prefix) { console.log(`%c${this.prefix} ${fullMessage}`, style); } else { console.log(`%c${fullMessage}`, style); } } } // 创建全局单例 const logger = new Logger(); // 导出单例和类 export { logger, Logger };