一个用于解决JSVMP海量日志导出技巧

在运行之前将以下这段代码贴进console中运行,然后执行需要的逻辑,待日志完全打印,手动在console执行 console.save(),即可立马将所有的日志导出为本地文件

接下来就可以安安心心在本地分析日志内容了

class VmpLogExporter {
    constructor(config = {}) {
        this.config = {
            maxConsoleLines: config.maxConsoleLines || 800,
            exportBatchSize: config.exportBatchSize || 500,
            autoExportInterval: config.autoExportInterval || 60000, // 1分钟
            enableAutoExport: config.enableAutoExport !== false,
            logPrefix: config.logPrefix || 'VMP_LOG',
            keepInConsole: config.keepInConsole || 100 // 控制台保留行数
        };
        
        this.logBuffer = [];
        this.fileCounter = 1;
        this.consoleLineCount = 0;
        this.isExporting = false;
        
        this.init();
    }
    
    init() {
        this.hijackConsole();
        if (this.config.enableAutoExport) {
            this.startAutoExport();
        }
        console.log(`🚀 VMP日志导出系统已启动 - 将自动保存日志避免浏览器崩溃`);
    }
    
    hijackConsole() {
        const originalMethods = {};
        ['log', 'warn', 'error', 'info', 'debug'].forEach(method => {
            originalMethods[method] = console[method];
            
            console[method] = (...args) => {
                // 记录到缓冲区
                this.addToBuffer(method, args);
                
                // 检查是否需要清理console
                this.consoleLineCount++;
                if (this.consoleLineCount >= this.config.maxConsoleLines) {
                    this.manageConsole();
                }
                
                // 正常输出到console
                return originalMethods[method].apply(console, args);
            };
        });
        
        this.originalMethods = originalMethods;
    }
    
    addToBuffer(level, args) {
        const timestamp = new Date().toISOString();
        const logEntry = {
            timestamp,
            level,
            args: this.serializeArgs(args),
            raw: args
        };
        
        this.logBuffer.push(logEntry);
        
        // 如果缓冲区过大,自动导出
        if (this.logBuffer.length >= this.config.exportBatchSize) {
            this.exportLogs();
        }
    }
    
    serializeArgs(args) {
        return args.map(arg => {
            if (typeof arg === 'object' && arg !== null) {
                try {
                    return JSON.stringify(arg, null, 2);
                } catch (e) {
                    return '[Circular Reference Object]';
                }
            }
            return String(arg);
        });
    }
    
    manageConsole() {
        if (this.isExporting) return;
        
        console.log(`📤 正在导出日志避免浏览器卡死...`);
        
        // 导出当前日志
        this.exportLogs();
        
        // 清理console但保留最新的一些日志
        console.clear();
        
        // 重新显示最近的重要日志
        const recentLogs = this.logBuffer.slice(-this.config.keepInConsole);
        recentLogs.forEach(log => {
            this.originalMethods[log.level].apply(console, [
                `[${log.timestamp}]`, 
                ...log.raw
            ]);
        });
        
        console.log(`🔄 Console已清理,保留了最近${recentLogs.length}条日志`);
        this.consoleLineCount = recentLogs.length;
    }
    
    async exportLogs() {
        if (this.isExporting || this.logBuffer.length === 0) return;
        
        this.isExporting = true;
        
        try {
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            const filename = `${this.config.logPrefix}_${timestamp}_part${this.fileCounter}.json`;
            
            const exportData = {
                exportInfo: {
                    timestamp: new Date().toISOString(),
                    partNumber: this.fileCounter,
                    totalLogs: this.logBuffer.length,
                    source: 'VMP_Logger'
                },
                logs: this.logBuffer
            };
            
            // 创建并下载文件
            const blob = new Blob([JSON.stringify(exportData, null, 2)], { 
                type: 'application/json' 
            });
            const url = URL.createObjectURL(blob);
            
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            a.style.display = 'none';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            
            console.log(`✅ 已导出 ${this.logBuffer.length} 条日志到文件: ${filename}`);
            
            // 清空缓冲区
            this.logBuffer = [];
            this.fileCounter++;
            
        } catch (error) {
            console.error('❌ 导出日志失败:', error);
        } finally {
            this.isExporting = false;
        }
    }
    
    startAutoExport() {
        setInterval(() => {
            if (this.logBuffer.length > 0) {
                console.log(`⏰ 定时导出日志: ${this.logBuffer.length} 条`);
                this.exportLogs();
            }
        }, this.config.autoExportInterval);
    }
    
    // 手动导出当前缓冲区
    manualExport() {
        console.log('🖱️ 手动导出日志...');
        this.exportLogs();
    }
    
    // 导出所有日志的合并版本
    exportAllLogs() {
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const filename = `${this.config.logPrefix}_COMPLETE_${timestamp}.txt`;
        
        const allLogs = this.logBuffer.map(log => {
            return `[${log.timestamp}] ${log.level.toUpperCase()}: ${log.args.join(' ')}`;
        }).join('\n');
        
        const blob = new Blob([allLogs], { type: 'text/plain' });
        const url = URL.createObjectURL(blob);
        
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        a.click();
        URL.revokeObjectURL(url);
        
        console.log(`📄 已导出完整文本日志: ${filename}`);
    }
    
    // 获取状态信息
    getStatus() {
        return {
            bufferSize: this.logBuffer.length,
            consoleLines: this.consoleLineCount,
            fileCounter: this.fileCounter,
            isExporting: this.isExporting
        };
    }
    
    // 恢复原始console
    restore() {
        ['log', 'warn', 'error', 'info', 'debug'].forEach(method => {
            console[method] = this.originalMethods[method];
        });
        console.log('🔄 已恢复原始console行为');
    }
}

// 使用示例和配置
const vmpLogger = new VmpLogExporter({
    maxConsoleLines: 1000000,      // console最大行数
    exportBatchSize: 1000000,      // 批量导出大小
    autoExportInterval: 60000, // 60秒自动导出
    enableAutoExport: false,    // 启用自动导出
    logPrefix: 'VMP_REVERSE',  // 文件前缀
    keepInConsole: 1000000      // console保留行数
});

// 全局访问接口
window.vmpLogger = vmpLogger;

// 添加快捷命令
console.save = () => vmpLogger.manualExport();
console.saveAll = () => vmpLogger.exportAllLogs();
console.status = () => console.table(vmpLogger.getStatus());

console.log(`
🎯 VMP逆向日志系统使用说明:
• 系统会自动管理console日志,避免浏览器卡死
• 所有日志都会保存,不会丢失任何VMP算法信息
• 快捷命令:
  - console.save() : 手动导出当前日志
  - console.saveAll() : 导出完整文本日志
  - console.status() : 查看系统状态
• 日志文件会自动下载到Downloads文件夹
• 每个JSON文件包含完整的时间戳和元数据
`);