当前位置: 首页 > news >正文

防爬虫逆向日志爆炸,精简追踪不崩浏览器控制台

概述

本文详细介绍了一种基于JavaScript的Web日志收集器实现,该实现支持多种数据类型检测、调用栈追踪和日志持久化功能避免控制台打印卡顿与奔溃。

技术背景

问题挑战

  1. 复杂数据类型处理:Web环境中存在多种数据类型,包括普通数组、类型化数组、函数、对象等
  2. 调用栈追踪:需要准确捕获和格式化方法调用链
  3. 性能监控:需要监控函数执行时间和性能指标
  4. 日志持久化:支持浏览器环境的日志保存

解决方案架构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   类型检测器      │    │   调用栈分析器    │    │   日志格式化器    │
│  TypeChecker    │    │ StackAnalyzer   │    │ LogFormatter    │
└─────────────────┘    └─────────────────┘    └─────────────────┘│                       │                       │└───────────────────────┼───────────────────────┘│┌─────────────────┐│    日志收集器     ││ WebLogCollector │└─────────────────┘│┌─────────────────┐│    持久化管理器   ││ LogPersistence  │└─────────────────┘

核心组件详解

1. 类型检测器 (TypeChecker)

类型检测器负责识别和处理各种JavaScript数据类型:

const TypeChecker = {// 检查是否为类型化数组isTypedArray(value) {return value instanceof ArrayBuffer ||value instanceof Int8Array ||value instanceof Uint8Array ||// ... 其他类型化数组},// 检查是否为普通数组isArray(value) {return Array.isArray(value);},// 检查是否为函数isFunction(value) {return typeof value === 'function';}
};

技术要点

  • 支持所有ES6+类型化数组类型
  • 区分普通数组和类型化数组
  • 处理函数和对象的特殊检测

2. 调用栈分析器 (StackAnalyzer)

调用栈分析器提供精确的方法调用追踪:

class StackAnalyzer {static getStackTrace() {try {throw new Error();} catch (error) {return error.stack;}}static parseCallChain(skipFrames = 2) {const stack = this.getStackTrace();const stackLines = stack.split('\n').slice(skipFrames);return stackLines.map(line => {const methodMatch = line.match(/at\s+(.+?)\s+\(/);const fileMatch = line.match(/\((.+?):(\d+):(\d+)\)/);if (methodMatch && fileMatch) {const fileName = fileMatch[1].split('/').pop();return `${methodMatch[1]}@${fileName}:${fileMatch[2]}`;}return methodMatch ? methodMatch[1] : line.trim();}).join(' → ');}
}

技术要点

  • 通过抛出错误获取调用栈信息
  • 正则表达式解析方法名和文件信息
  • 支持跳过指定数量的栈帧

3. 日志格式化器 (LogFormatter)

日志格式化器处理各种数据类型的序列化:

class LogFormatter {static formatValue(value, depth = 0) {const maxDepth = 3;if (depth > maxDepth) return '[深度超限]';if (value === null) return 'null';if (value === undefined) return 'undefined';if (TypeChecker.isFunction(value)) {return `[Function: ${value.name || 'anonymous'}]`;}if (TypeChecker.isArray(value)) {if (value.some(item => TypeChecker.isObject(item))) {return `[${value.map(item => this.formatValue(item, depth + 1)).join(', ')}]`;}return `[${value.join(', ')}] (长度: ${value.length})`;}if (TypeChecker.isTypedArray(value)) {return `[${value.constructor.name}] (长度: ${value.length})`;}if (TypeChecker.isObject(value)) {try {return JSON.stringify(value, null, 2);} catch (error) {if (error.message.includes('circular')) {return '[循环引用对象]';}return `[对象: ${value.constructor?.name || 'Object'}]`;}}return String(value);}
}

技术要点

  • 递归深度控制防止无限循环
  • 循环引用检测和处理
  • 类型化数组特殊处理
  • 函数名提取和格式化

4. 日志持久化管理器 (LogPersistence)

支持多环境的日志保存:

class LogPersistence {constructor() {this.isNodeEnvironment = typeof window === 'undefined';}saveToFile(content, filename) {if (this.isNodeEnvironment) {this.saveToNodeFile(content, filename);} else {this.saveToBrowserFile(content, filename);}}saveToBrowserFile(content, filename) {const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });const url = URL.createObjectURL(blob);const link = document.createElement('a');link.href = url;link.download = filename;link.style.display = 'none';document.body.appendChild(link);link.click();document.body.removeChild(link);URL.revokeObjectURL(url);}
}

技术要点

  • 环境检测和适配
  • Blob API用于浏览器文件下载
  • 内存管理(URL.revokeObjectURL)

主日志收集器实现

核心功能

class WebLogCollector {constructor(options = {}) {this.options = {enableConsole: true,maxLogCount: 10000,autoSave: true,logLevel: 'info',enableStackTrace: true,...options};this.logs = [];this.logCounter = 0;this.persistence = new LogPersistence();this.startTime = Date.now();this.bindToGlobal();}recordLog(level, ...args) {const timestamp = LogFormatter.formatTimestamp();const callChain = this.options.enableStackTrace ? StackAnalyzer.parseCallChain() : 'N/A';const methodName = StackAnalyzer.getCurrentMethod();const formattedArgs = args.map(arg => LogFormatter.formatValue(arg)).join(' ');const logEntry = {id: ++this.logCounter,timestamp,level,method: methodName,callChain,message: formattedArgs,duration: Date.now() - this.startTime};this.logs.push(logEntry);if (this.options.enableConsole) {const consoleMethod = console[level] || console.log;consoleMethod(`[${timestamp}] [${level.toUpperCase()}] ${formattedArgs}`);}if (this.options.autoSave && this.logs.length >= this.options.maxLogCount) {this.exportLogs();}}
}

性能监控功能

// 同步性能监控
performance(name, fn) {const start = performance.now();this.info(`开始执行: ${name}`);try {const result = fn();const end = performance.now();this.info(`完成执行: ${name}, 耗时: ${(end - start).toFixed(2)}ms`);return result;} catch (error) {const end = performance.now();this.error(`执行失败: ${name}, 耗时: ${(end - start).toFixed(2)}ms, 错误: ${error.message}`);throw error;}
}// 异步性能监控
async performanceAsync(name, fn) {const start = performance.now();this.info(`开始执行异步任务: ${name}`);try {const result = await fn();const end = performance.now();this.info(`完成异步任务: ${name}, 耗时: ${(end - start).toFixed(2)}ms`);return result;} catch (error) {const end = performance.now();this.error(`异步任务失败: ${name}, 耗时: ${(end - start).toFixed(2)}ms, 错误: ${error.message}`);throw error;}
}

使用示例

基础使用

// 初始化日志收集器
const collector = new WebLogCollector({enableConsole: true,maxLogCount: 5000,autoSave: true
});// 基础日志记录
collector.info('用户登录', { userId: 123, username: 'john' });
collector.warn('API响应时间过长', { duration: 5000 });
collector.error('数据库连接失败', error);// 性能监控
const result = collector.performance('数据处理', () => {return processData(largeDataset);
});// 异步性能监控
const asyncResult = await collector.performanceAsync('API调用', async () => {return await fetch('/api/data');
});

爬虫场景应用

// 爬虫页面分析
collector.info('开始爬取页面', { url: targetUrl });
collector.debug('页面元素数量', document.querySelectorAll('*').length);// 数据提取监控
collector.performance('数据提取', () => {const data = extractDataFromPage();collector.info('提取数据完成', { count: data.length });return data;
});// 错误处理
try {await performCrawling();
} catch (error) {collector.error('爬虫执行失败', {error: error.message,stack: error.stack,url: window.location.href});
}

日志导出和分析

// 导出不同格式的日志
collector.exportLogs('json');  // JSON格式
collector.exportLogs('txt');  // 文本格式
collector.exportLogs('csv');  // CSV格式// 获取统计信息
const stats = collector.getStatistics();
console.log('日志统计:', stats);
/*
输出示例:
{totalLogs: 1250,byLevel: { info: 800, warn: 200, error: 50 },byMethod: { 'crawlPage': 100, 'extractData': 150 },averageDuration: 125.5,startTime: 1640995200000,currentTime: 1640995800000
}
*/

高级特性

1. 内存管理

// 自动清理机制
if (this.logs.length >= this.options.maxLogCount) {this.exportLogs();this.logs = []; // 清空内存
}

2. 循环引用处理

// 检测和处理循环引用
try {return JSON.stringify(value, null, 2);
} catch (error) {if (error.message.includes('circular')) {return '[循环引用对象]';}return `[对象: ${value.constructor?.name || 'Object'}]`;
}

性能优化建议

1. 日志级别控制

const collector = new WebLogCollector({logLevel: 'warn', // 只记录警告和错误enableStackTrace: false // 生产环境关闭调用栈追踪
});

2. 批量处理

// 批量记录日志
const batchLogs = [];
for (let i = 0; i < 1000; i++) {batchLogs.push({ id: i, data: `item-${i}` });
}
collector.info('批量数据', batchLogs);

3. 异步日志处理

// 异步日志记录
async function asyncLog(level, ...args) {return new Promise(resolve => {setTimeout(() => {collector.recordLog(level, ...args);resolve();}, 0);});
}

最佳实践

1. 日志结构化

// 结构化日志记录
collector.info('用户登陆信息', {action: 'login',userId: user.id,timestamp: Date.now(),userAgent: navigator.userAgent,sessionId: session.id
});

2. 错误上下文

// 完整的错误上下文
try {await riskyOperation();
} catch (error) {collector.error('操作失败', {error: error.message,stack: error.stack,context: {userId: currentUser.id,operation: 'dataProcessing',inputData: sanitizedInput}});
}

3. 性能监控

// 关键路径性能监控
collector.performance('关键业务逻辑', () => {return executeBusinessLogic();
});

源码

/*** Web日志收集器 - 用于爬虫注入和调试追踪* 支持多种数据类型检测、调用栈追踪和日志持久化*/// 类型化数组检测工具
const TypeChecker = {// 检查是否为类型化数组isTypedArray(value) {return value instanceof ArrayBuffer ||value instanceof Int8Array ||value instanceof Uint8Array ||value instanceof Uint8ClampedArray ||value instanceof Int16Array ||value instanceof Uint16Array ||value instanceof Int32Array ||value instanceof Uint32Array ||value instanceof Float32Array ||value instanceof Float64Array ||value instanceof BigInt64Array ||value instanceof BigUint64Array;},// 检查是否为普通数组isArray(value) {return Array.isArray(value);},// 检查是否为函数isFunction(value) {return typeof value === 'function';},// 检查是否为对象isObject(value) {return value !== null && typeof value === 'object';}
};// 调用栈分析器
class StackAnalyzer {/*** 获取当前调用栈信息* @returns {string} 格式化的调用栈字符串*/static getStackTrace() {try {throw new Error();} catch (error) {return error.stack;}}/*** 解析并格式化调用栈* @param {number} skipFrames - 跳过的栈帧数量* @returns {string} 格式化的调用链*/static parseCallChain(skipFrames = 2) {const stack = this.getStackTrace();const stackLines = stack.split('\n').slice(skipFrames);const methodNames = stackLines.map(line => {// 匹配方法名和文件信息const methodMatch = line.match(/at\s+(.+?)\s+\(/);const fileMatch = line.match(/\((.+?):(\d+):(\d+)\)/);if (methodMatch) {const methodName = methodMatch[1];if (fileMatch) {const fileName = fileMatch[1].split('/').pop();const lineNumber = fileMatch[2];return `${methodName}@${fileName}:${lineNumber}`;}return methodName;}return line.trim();});return methodNames.filter(name => name && !name.includes('StackAnalyzer')).join(' → ');}/*** 获取当前执行的方法名* @returns {string} 当前方法名*/static getCurrentMethod() {const stack = this.getStackTrace();const lines = stack.split('\n');const currentLine = lines[2] || lines[1];const match = currentLine.match(/at\s+(.+?)\s+\(/);return match ? match[1] : 'unknown';}
}// 日志格式化器
class LogFormatter {/*** 格式化日志参数* @param {any} value - 要格式化的值* @param {number} depth - 递归深度* @returns {string} 格式化后的字符串*/static formatValue(value, depth = 0) {const maxDepth = 3;if (depth > maxDepth) {return '[深度超限]';}if (value === null) return 'null';if (value === undefined) return 'undefined';if (TypeChecker.isFunction(value)) {return `[Function: ${value.name || 'anonymous'}]`;}if (TypeChecker.isArray(value)) {if (value.length === 0) return '[]';if (value.some(item => TypeChecker.isObject(item))) {return `[${value.map(item => this.formatValue(item, depth + 1)).join(', ')}]`;}return `[${value.join(', ')}] (长度: ${value.length})`;}if (TypeChecker.isTypedArray(value)) {return `[${value.constructor.name}] (长度: ${value.length})`;}if (TypeChecker.isObject(value)) {try {return JSON.stringify(value, null, 2);} catch (error) {if (error.name === 'TypeError' && error.message.includes('circular')) {return '[循环引用对象]';}return `[对象: ${value.constructor?.name || 'Object'}]`;}}return String(value);}/*** 格式化时间戳* @returns {string} 格式化的时间戳*/static formatTimestamp() {const now = new Date();return now.toISOString().replace('T', ' ').replace('Z', '');}
}// 日志持久化管理器
class LogPersistence {constructor() {this.isNodeEnvironment = typeof window === 'undefined';}/*** 保存日志到文件(浏览器环境)* @param {string} content - 日志内容* @param {string} filename - 文件名*/saveToFile(content, filename) {if (this.isNodeEnvironment) {this.saveToNodeFile(content, filename);} else {this.saveToBrowserFile(content, filename);}}/*** 浏览器环境保存* @param {string} content - 日志内容* @param {string} filename - 文件名*/saveToBrowserFile(content, filename) {const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });const url = URL.createObjectURL(blob);const link = document.createElement('a');link.href = url;link.download = filename;link.style.display = 'none';document.body.appendChild(link);link.click();document.body.removeChild(link);URL.revokeObjectURL(url);}/*** Node.js环境保存* @param {string} content - 日志内容* @param {string} filename - 文件名*/saveToNodeFile(content, filename) {try {const fs = require('fs');const path = require('path');const os = require('os');const logDir = path.join(os.homedir(), 'web-logs');if (!fs.existsSync(logDir)) {fs.mkdirSync(logDir, { recursive: true });}const filePath = path.join(logDir, filename);fs.writeFileSync(filePath, content, 'utf8');console.log(`日志已保存到: ${filePath}`);} catch (error) {console.error('保存日志文件失败:', error);}}
}// 主日志收集器类
class WebLogCollector {constructor(options = {}) {this.options = {enableConsole: true,maxLogCount: 10000,autoSave: true,logLevel: 'info',enableStackTrace: true,...options};this.logs = [];this.logCounter = 0;this.persistence = new LogPersistence();this.startTime = Date.now();// 绑定方法到全局作用域this.bindToGlobal();}/*** 绑定方法到全局作用域*/bindToGlobal() {if (typeof window !== 'undefined') {window.logCollector = this;window.trace = this.trace.bind(this);window.debug = this.debug.bind(this);window.info = this.info.bind(this);window.warn = this.warn.bind(this);window.error = this.error.bind(this);}}/*** 记录日志* @param {string} level - 日志级别* @param {any} args - 日志参数*/recordLog(level, ...args) {const timestamp = LogFormatter.formatTimestamp();const callChain = this.options.enableStackTrace ? StackAnalyzer.parseCallChain() : 'N/A';const methodName = StackAnalyzer.getCurrentMethod();const formattedArgs = args.map(arg => LogFormatter.formatValue(arg)).join(' ');const logEntry = {id: ++this.logCounter,timestamp,level,method: methodName,callChain,message: formattedArgs,duration: Date.now() - this.startTime};this.logs.push(logEntry);if (this.options.enableConsole) {const consoleMethod = console[level] || console.log;consoleMethod(`[${timestamp}] [${level.toUpperCase()}] ${formattedArgs}`);}if (this.options.autoSave && this.logs.length >= this.options.maxLogCount) {this.exportLogs();}}/*** 追踪级别日志* @param {...any} args - 日志参数*/trace(...args) {this.recordLog('trace', ...args);}/*** 调试级别日志* @param {...any} args - 日志参数*/debug(...args) {this.recordLog('debug', ...args);}/*** 信息级别日志* @param {...any} args - 日志参数*/info(...args) {this.recordLog('info', ...args);}/*** 警告级别日志* @param {...any} args - 日志参数*/warn(...args) {this.recordLog('warn', ...args);}/*** 错误级别日志* @param {...any} args - 日志参数*/error(...args) {this.recordLog('error', ...args);}/*** 性能监控日志* @param {string} name - 性能标记名称* @param {Function} fn - 要监控的函数* @returns {any} 函数执行结果*/performance(name, fn) {const start = performance.now();this.info(`开始执行: ${name}`);try {const result = fn();const end = performance.now();this.info(`完成执行: ${name}, 耗时: ${(end - start).toFixed(2)}ms`);return result;} catch (error) {const end = performance.now();this.error(`执行失败: ${name}, 耗时: ${(end - start).toFixed(2)}ms, 错误: ${error.message}`);throw error;}}/*** 异步性能监控* @param {string} name - 性能标记名称* @param {Function} fn - 要监控的异步函数* @returns {Promise<any>} 函数执行结果*/async performanceAsync(name, fn) {const start = performance.now();this.info(`开始执行异步任务: ${name}`);try {const result = await fn();const end = performance.now();this.info(`完成异步任务: ${name}, 耗时: ${(end - start).toFixed(2)}ms`);return result;} catch (error) {const end = performance.now();this.error(`异步任务失败: ${name}, 耗时: ${(end - start).toFixed(2)}ms, 错误: ${error.message}`);throw error;}}/*** 导出日志* @param {string} format - 导出格式 ('json' | 'txt' | 'csv')*/exportLogs(format = 'json') {const timestamp = new Date().toISOString().replace(/[:.]/g, '-');let content, filename;switch (format) {case 'json':content = JSON.stringify(this.logs, null, 2);filename = `web-logs-${timestamp}.json`;break;case 'txt':content = this.logs.map(log => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.method}: ${log.message}`).join('\n');filename = `web-logs-${timestamp}.txt`;break;case 'csv':content = 'ID,Timestamp,Level,Method,CallChain,Message,Duration\n' +this.logs.map(log => `${log.id},"${log.timestamp}",${log.level},"${log.method}","${log.callChain}","${log.message}",${log.duration}`).join('\n');filename = `web-logs-${timestamp}.csv`;break;default:throw new Error(`不支持的导出格式: ${format}`);}this.persistence.saveToFile(content, filename);this.logs = []; // 清空日志}/*** 获取日志统计信息* @returns {Object} 统计信息*/getStatistics() {const stats = {totalLogs: this.logs.length,byLevel: {},byMethod: {},averageDuration: 0,startTime: this.startTime,currentTime: Date.now()};this.logs.forEach(log => {stats.byLevel[log.level] = (stats.byLevel[log.level] || 0) + 1;stats.byMethod[log.method] = (stats.byMethod[log.method] || 0) + 1;});if (this.logs.length > 0) {stats.averageDuration = this.logs.reduce((sum, log) => sum + log.duration, 0) / this.logs.length;}return stats;}/*** 清空日志*/clear() {this.logs = [];this.logCounter = 0;this.startTime = Date.now();}
}// 创建全局实例
const webLogCollector = new WebLogCollector();

总结

本文介绍的Web日志收集器提供了完整的日志记录、调用栈追踪和性能监控解决方案。该方案适用于各种Web应用场景,有效避免控制台上万条打印导致浏览器奔溃。

http://www.dtcms.com/a/419455.html

相关文章:

  • DevExpress WinForms v25.2新功能预览 - 即将升级富文本编辑器控件功能
  • C#知识学习-015(修饰符_4)
  • 有做分期海淘的网站吗苏州建设网站找网络公司
  • 全缓冲和行缓冲
  • 【C++语法】C++11——右值引用
  • 如何给网站做app平台网站建设 厦门
  • 鞍山网站建设工作室北京交易中心网站
  • 现代C++ Lambda表达式:最佳实践、深入理解和资源推荐
  • CUDA 内核中计算全局线程索引的标准方法
  • 刚做网站做多用户还是单用户企业建站的作用是什么
  • CUDA 13.0深度解析:统一ARM生态、UVM增强与GPU共享的革命
  • 佛山外贸网站建设咨询php网站上做微信支付功能
  • 公司网站中新闻中心怎样做优化网页设计图片怎么居中
  • 网站运营经验山东查询网站备案
  • 巴塘网站建设网站开发的论文课题
  • GuavaCache
  • 免费空间如何放网站搜索引擎优化培训免费咨询
  • LeetCode 53 最大子数字和(动态规划)
  • 如何为100Tops机器人“退烧”?世强芯片热管理方案,释放100%算力!
  • 【NodeJS】使用 NVM 安装 Node.js 22 并配置国内镜像加速
  • 边缘计算与AI:移动端设计软件的实时性能突破
  • 芜湖有没有网站建设公司吗wordpress邮件分析插件
  • 网上做外贸都有哪些网站组织架构及营销网络怎么填写
  • 网站建设费开票税收代码模板网站好还是自助建站好
  • 苏州网站建设数据网络wordpress添加广告插件
  • 江西哪里可以做企业网站h5案例网站
  • 洛谷题解——C语言(9.17——9.19)
  • vue3 element-plus自定义el-select后缀图标
  • 突破速度瓶颈:为可道云连接雨云对象存储,实现私人网盘高速上传下载
  • 第二章:模块的编译与运行-6 Compiling and Loading