一、为什么要做前端日志系统

想象一下这样的场景:用户在你的网站上操作时突然报错了,但开发者完全不知道发生了什么。这时候如果有套日志系统,就能像黑匣子一样记录下用户的操作路径和错误信息,问题排查效率能提升10倍。

前端日志系统主要解决两个核心问题:

  1. 错误收集:自动捕获代码运行时异常、接口报错等
  2. 行为追踪:记录用户点击、页面跳转等关键操作

二、基础搭建:jQuery错误监控

技术栈:jQuery + Web API

// 全局错误监控
$(document).ajaxError(function(event, jqxhr, settings, error) {
    // 组装错误数据
    const logData = {
        type: 'API_ERROR',
        url: settings.url,
        method: settings.type,
        status: jqxhr.status,
        error: error,
        timestamp: new Date().toISOString()
    };
    
    // 发送到日志服务器
    sendLog(logData);
});

// 未捕获的异常监控
window.onerror = function(message, source, lineno, colno, error) {
    const logData = {
        type: 'JS_ERROR',
        message: message,
        source: source,
        line: lineno,
        column: colno,
        stack: error && error.stack,
        timestamp: new Date().toISOString()
    };
    
    sendLog(logData);
};

// 日志发送函数
function sendLog(data) {
    $.ajax({
        url: '/api/logs',
        method: 'POST',
        contentType: 'application/json',
        data: JSON.stringify(data),
        timeout: 3000 // 3秒超时
    });
}

这个基础版本已经能捕获两种常见错误:

  1. AJAX请求失败(接口500错误等)
  2. JavaScript运行时错误(undefined变量等)

三、增强版:用户行为追踪

技术栈:jQuery + Web Storage

// 用户行为追踪器
const BehaviorTracker = {
    MAX_RECORDS: 50, // 最大记录数
    STORAGE_KEY: 'user_behavior',
    
    init: function() {
        // 从本地存储加载已有记录
        this.records = JSON.parse(
            localStorage.getItem(this.STORAGE_KEY) || '[]'
        );
        
        // 绑定事件监听
        this.bindEvents();
        
        // 定时上传
        setInterval(this.uploadRecords.bind(this), 10000);
    },
    
    bindEvents: function() {
        // 追踪点击事件
        $(document).on('click', '[data-track]', function(e) {
            const target = $(this);
            this.addRecord({
                type: 'CLICK',
                element: target.data('track'),
                text: target.text().trim(),
                timestamp: new Date().toISOString()
            });
        }.bind(this));
        
        // 追踪页面浏览
        $(window).on('beforeunload', function() {
            this.addRecord({
                type: 'PAGE_VIEW',
                path: location.pathname,
                timestamp: new Date().toISOString()
            });
        }.bind(this));
    },
    
    addRecord: function(record) {
        this.records.push(record);
        
        // 控制记录数量
        if (this.records.length > this.MAX_RECORDS) {
            this.records.shift();
        }
        
        localStorage.setItem(
            this.STORAGE_KEY,
            JSON.stringify(this.records)
        );
    },
    
    uploadRecords: function() {
        if (this.records.length === 0) return;
        
        $.ajax({
            url: '/api/behavior',
            method: 'POST',
            data: JSON.stringify(this.records),
            success: function() {
                // 上传成功后清空本地记录
                this.records = [];
                localStorage.removeItem(this.STORAGE_KEY);
            }.bind(this)
        });
    }
};

// 初始化追踪器
$(document).ready(function() {
    BehaviorTracker.init();
});

这个增强版实现了:

  1. 关键元素点击追踪(通过data-track属性标记)
  2. 页面浏览记录
  3. 本地存储缓冲,避免频繁请求
  4. 定时批量上传机制

四、实战优化:性能与可靠性

技术栈:jQuery + IndexedDB

// 高性能日志存储器
const LogDB = {
    DB_NAME: 'FrontendLogs',
    DB_VERSION: 1,
    STORE_NAME: 'logs',
    
    db: null,
    
    // 初始化数据库
    init: function(callback) {
        const request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
        
        request.onupgradeneeded = function(e) {
            const db = e.target.result;
            if (!db.objectStoreNames.contains(this.STORAGE_NAME)) {
                db.createObjectStore(this.STORE_NAME, {
                    keyPath: 'id',
                    autoIncrement: true
                });
            }
        }.bind(this);
        
        request.onsuccess = function(e) {
            this.db = e.target.result;
            callback && callback();
        }.bind(this);
    },
    
    // 添加日志
    addLog: function(log) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([this.STORE_NAME], 'readwrite');
            const store = transaction.objectStore(this.STORE_NAME);
            
            const request = store.add({
                ...log,
                createdAt: new Date().getTime()
            });
            
            request.onsuccess = resolve;
            request.onerror = reject;
        });
    },
    
    // 获取所有日志
    getAllLogs: function() {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([this.STORE_NAME], 'readonly');
            const store = transaction.objectStore(this.STORE_NAME);
            
            const request = store.getAll();
            
            request.onsuccess = function() {
                resolve(request.result || []);
            };
            request.onerror = reject;
        });
    },
    
    // 清除日志
    clearLogs: function() {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([this.STORE_NAME], 'readwrite');
            const store = transaction.objectStore(this.STORE_NAME);
            
            const request = store.clear();
            
            request.onsuccess = resolve;
            request.onerror = reject;
        });
    }
};

// 使用示例
$(document).ready(function() {
    LogDB.init(function() {
        // 数据库初始化完成后
        window.onerror = function(...args) {
            const log = formatErrorLog(args);
            LogDB.addLog(log);
        };
        
        // 定时上传
        setInterval(function() {
            LogDB.getAllLogs().then(logs => {
                if (logs.length > 0) {
                    return $.ajax({
                        url: '/api/logs/batch',
                        method: 'POST',
                        data: JSON.stringify(logs)
                    }).then(function() {
                        return LogDB.clearLogs();
                    });
                }
            });
        }, 15000); // 每15秒上传一次
    });
});

这个优化版本的特点是:

  1. 使用IndexedDB存储日志,容量更大(相比localStorage)
  2. 异步API设计,避免阻塞主线程
  3. 批量上传机制,减少网络请求
  4. 断网时自动保存,网络恢复后继续上传

五、应用场景与注意事项

典型应用场景

  1. 生产环境错误监控:快速定位线上问题
  2. 用户行为分析:了解用户操作路径
  3. 性能优化:记录页面加载耗时等指标
  4. A/B测试:追踪不同版本的转化率

技术优缺点

优点:

  • 实现成本低,jQuery项目可快速接入
  • 不依赖第三方服务,数据自主可控
  • 轻量级,对页面性能影响小

缺点:

  • 需要自行搭建后端接收服务
  • 大数据量时需要考虑存储优化
  • 用户可能禁用JavaScript导致失效

注意事项

  1. 隐私保护:避免记录敏感信息(如密码)
  2. 性能影响:控制日志采集频率
  3. 数据清洗:后端要做好日志处理
  4. 采样率:高流量网站要考虑采样策略

六、完整实现方案

技术栈:jQuery + Web API + IndexedDB

// 完整配置对象
const LoggerConfig = {
    appId: 'YOUR_APP_ID',      // 应用标识
    reportUrl: '/api/logs',    // 上报地址
    maxRetry: 3,               // 最大重试次数
    sampling: 0.1,            // 采样率(10%)
    version: '1.0.0'           // SDK版本
};

// 主日志类
class FrontendLogger {
    constructor(config) {
        this.config = {...LoggerConfig, ...config};
        this.retryCount = 0;
        this.init();
    }
    
    init() {
        // 初始化数据库
        this.db = new LogDB();
        this.db.init(() => {
            // 错误监控
            this.setupErrorHandling();
            
            // 行为追踪
            this.setupBehaviorTracking();
            
            // 性能监控
            this.setupPerformance();
        });
    }
    
    setupErrorHandling() {
        // 捕获全局错误
        window.onerror = this.handleWindowError.bind(this);
        
        // 捕获Promise错误
        window.addEventListener('unhandledrejection', this.handlePromiseError.bind(this));
        
        // 捕获AJAX错误
        $(document).ajaxError(this.handleAjaxError.bind(this));
    }
    
    setupBehaviorTracking() {
        // 页面浏览
        $(window).on('load beforeunload', this.trackPageView.bind(this));
        
        // 按钮点击
        $(document).on('click', '[data-track]', this.trackClick.bind(this));
    }
    
    setupPerformance() {
        // 使用Performance API获取性能数据
        if (window.performance) {
            setTimeout(() => {
                const timing = performance.timing;
                const metrics = {
                    dns: timing.domainLookupEnd - timing.domainLookupStart,
                    tcp: timing.connectEnd - timing.connectStart,
                    ttfb: timing.responseStart - timing.requestStart,
                    download: timing.responseEnd - timing.responseStart,
                    domReady: timing.domComplete - timing.domLoading,
                    load: timing.loadEventEnd - timing.navigationStart
                };
                
                this.saveLog({
                    type: 'PERFORMANCE',
                    metrics: metrics
                });
            }, 0);
        }
    }
    
    // 处理错误的方法...
    // 保存日志的方法...
    // 上报日志的方法...
}

// 使用示例
$(document).ready(function() {
    const logger = new FrontendLogger({
        appId: 'my_ecommerce_site'
    });
    
    // 也可以手动记录业务日志
    $('#checkoutBtn').click(function() {
        logger.saveLog({
            type: 'BUSINESS',
            action: 'checkout',
            items: cart.getItems()
        });
    });
});

这个完整实现包含:

  1. 模块化设计,易于扩展
  2. 完整的错误监控体系
  3. 灵活的行为追踪配置
  4. 性能数据采集
  5. 采样率控制等生产级功能

七、总结与建议

实现一个前端日志系统并不复杂,关键是要考虑清楚你的业务需要什么样的数据。对于大多数项目,建议从基础版本开始,随着业务增长再逐步完善。

部署后要重点关注:

  1. 日志数据的有效性:避免收集无用数据
  2. 系统稳定性:不要因为日志系统导致主业务崩溃
  3. 数据安全:做好敏感信息过滤

最后提醒,jQuery虽然现在不是主流了,但在老项目中仍然广泛使用。这套方案可以很好地帮助维护老项目,快速建立监控能力。