欢迎光临
我们一直在努力

前端监控体系建设:从异常捕获到性能分析

前端监控体系建设:从异常捕获到性能分析

大家好,我是蔓蔓。在大厂工作时,我负责搭建了团队的前端监控体系。一个好的监控系统能够帮助我们及时发现问题、定位问题、解决问题。今天我来和大家分享如何从零开始构建一套完整的前端监控体系。

监控体系概述

为什么需要监控

  • 及时发现问题:在用户反馈之前发现并解决问题
  • 快速定位问题:提供详细的错误信息和上下文
  • 优化用户体验:通过性能监控发现瓶颈
  • 数据驱动决策:基于真实数据进行优化
  • 监控分类

    监控类型监控内容工具推荐
    错误监控 JS错误、资源加载失败、接口错误 Sentry、Bugsnag
    性能监控 页面加载、首屏时间、FPS Lighthouse、Performance API
    用户行为 点击、页面浏览、表单提交 Google Analytics、Segment
    业务指标 转化率、留存率、DAU 自定义上报

    错误监控

    捕获JS错误

    // 全局错误捕获
    window.addEventListener('error', (event) => {
    const errorInfo = {
    type: 'javascript_error',
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error?.stack,
    timestamp: Date.now(),
    url: window.location.href,
    userAgent: navigator.userAgent
    };

    // 上报错误
    reportError(errorInfo);
    });

    // Promise 未捕获异常
    window.addEventListener('unhandledrejection', (event) => {
    const errorInfo = {
    type: 'promise_rejection',
    reason: event.reason?.message || String(event.reason),
    timestamp: Date.now(),
    url: window.location.href
    };

    reportError(errorInfo);
    });

    捕获资源加载错误

    // 监听资源加载失败
    document.addEventListener('error', (event) => {
    const target = event.target;

    if (target.tagName === 'SCRIPT' ||
    target.tagName === 'LINK' ||
    target.tagName === 'IMG') {

    const errorInfo = {
    type: 'resource_error',
    resourceType: target.tagName.toLowerCase(),
    src: target.src || target.href,
    timestamp: Date.now(),
    url: window.location.href
    };

    reportError(errorInfo);
    }
    }, true);

    捕获接口错误

    // 重写 fetch
    const originalFetch = window.fetch;

    window.fetch = async function(…args) {
    const [url, options] = args;
    const startTime = Date.now();

    try {
    const response = await originalFetch(…args);

    // 检查 HTTP 状态码
    if (!response.ok) {
    const errorInfo = {
    type: 'api_error',
    url,
    method: options?.method || 'GET',
    status: response.status,
    duration: Date.now() – startTime,
    timestamp: Date.now()
    };

    reportError(errorInfo);
    }

    return response;
    } catch (error) {
    const errorInfo = {
    type: 'api_error',
    url,
    method: options?.method || 'GET',
    error: error.message,
    duration: Date.now() – startTime,
    timestamp: Date.now()
    };

    reportError(errorInfo);
    throw error;
    }
    };

    性能监控

    使用 Performance API

    // 获取页面加载性能数据
    function getPerformanceData() {
    const performance = window.performance;
    const timing = performance.timing;

    return {
    // DNS 查询时间
    dnsTime: timing.domainLookupEnd – timing.domainLookupStart,

    // TCP 连接时间
    tcpTime: timing.connectEnd – timing.connectStart,

    // 请求响应时间
    requestTime: timing.responseEnd – timing.requestStart,

    // DOM 解析时间
    domParseTime: timing.domComplete – timing.domInteractive,

    // 首字节时间
    ttfb: timing.responseStart – timing.navigationStart,

    // 页面加载总时间
    loadTime: timing.loadEventEnd – timing.navigationStart,

    // DOMContentLoaded 时间
    domContentLoadedTime: timing.domContentLoadedEventEnd – timing.navigationStart
    };
    }

    // 上报性能数据
    window.addEventListener('load', () => {
    const performanceData = getPerformanceData();
    reportPerformance(performanceData);
    });

    自定义性能指标

    // 自定义计时
    function startTimer(name) {
    performance.mark(`${name}_start`);
    }

    function endTimer(name) {
    performance.mark(`${name}_end`);
    performance.measure(name, `${name}_start`, `${name}_end`);

    const measure = performance.getEntriesByName(name)[0];

    if (measure) {
    reportPerformance({
    name,
    duration: measure.duration,
    timestamp: Date.now()
    });

    // 清理标记
    performance.clearMarks(`${name}_start`);
    performance.clearMarks(`${name}_end`);
    performance.clearMeasures(name);
    }
    }

    // 使用示例
    startTimer('api_request');
    await fetch('/api/data');
    endTimer('api_request');

    FPS 监控

    // FPS 监控
    class FPSMonitor {
    constructor() {
    this.frameCount = 0;
    this.lastTime = performance.now();
    this.fps = 60;
    }

    start() {
    this.animationId = requestAnimationFrame(() => this.tick());
    }

    stop() {
    if (this.animationId) {
    cancelAnimationFrame(this.animationId);
    }
    }

    tick() {
    this.frameCount++;
    const currentTime = performance.now();

    if (currentTime – this.lastTime >= 1000) {
    this.fps = Math.round(this.frameCount * 1000 / (currentTime – this.lastTime));
    this.frameCount = 0;
    this.lastTime = currentTime;

    // 上报 FPS
    reportPerformance({
    name: 'fps',
    value: this.fps,
    timestamp: Date.now()
    });
    }

    this.animationId = requestAnimationFrame(() => this.tick());
    }
    }

    // 使用示例
    const fpsMonitor = new FPSMonitor();
    fpsMonitor.start();

    用户行为监控

    点击事件追踪

    // 自动追踪点击事件
    document.addEventListener('click', (event) => {
    const target = event.target;

    // 只追踪有 data-track 属性的元素
    if (target.dataset.track) {
    const eventData = {
    type: 'click',
    action: target.dataset.track,
    label: target.dataset.trackLabel || target.textContent,
    timestamp: Date.now(),
    url: window.location.href,
    position: {
    x: event.clientX,
    y: event.clientY
    }
    };

    reportBehavior(eventData);
    }
    }, true);

    页面浏览追踪

    // 页面浏览追踪
    class PageTracker {
    constructor() {
    this.pageStartTime = Date.now();
    this.currentPage = window.location.pathname;
    }

    trackPageView() {
    const pageData = {
    type: 'page_view',
    page: window.location.pathname,
    referrer: document.referrer,
    timestamp: Date.now()
    };

    reportBehavior(pageData);
    }

    trackPageLeave() {
    const pageData = {
    type: 'page_leave',
    page: this.currentPage,
    duration: Date.now() – this.pageStartTime,
    timestamp: Date.now()
    };

    reportBehavior(pageData);
    }
    }

    // 使用示例
    const pageTracker = new PageTracker();
    pageTracker.trackPageView();

    // 页面离开时上报
    window.addEventListener('beforeunload', () => {
    pageTracker.trackPageLeave();
    });

    数据上报

    上报策略

    class Reporter {
    constructor() {
    this.queue = [];
    this.batchSize = 10;
    this.maxQueueSize = 100;
    this.flushInterval = 5000;

    // 定时上报
    setInterval(() => this.flush(), this.flushInterval);

    // 页面卸载时上报
    window.addEventListener('beforeunload', () => this.flush(true));
    }

    add(data) {
    if (this.queue.length >= this.maxQueueSize) {
    // 超出队列大小,丢弃最早的数据
    this.queue.shift();
    }

    this.queue.push({
    …data,
    timestamp: Date.now(),
    sessionId: this.getSessionId()
    });

    // 达到批量大小立即上报
    if (this.queue.length >= this.batchSize) {
    this.flush();
    }
    }

    async flush(force = false) {
    if (this.queue.length === 0) return;

    const data = […this.queue];
    this.queue = [];

    try {
    await fetch('/api/report', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json'
    },
    body: JSON.stringify(data),
    // 确保页面卸载时也能发送
    keepalive: force
    });
    } catch (error) {
    // 上报失败,放回队列
    this.queue = […data, …this.queue];
    }
    }

    getSessionId() {
    let sessionId = localStorage.getItem('sessionId');

    if (!sessionId) {
    sessionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    localStorage.setItem('sessionId', sessionId);
    }

    return sessionId;
    }
    }

    // 全局实例
    export const reporter = new Reporter();

    数据可视化

    仪表盘设计

    // 简化版仪表盘组件
    class Dashboard {
    constructor(container) {
    this.container = container;
    this.data = {};
    }

    update(data) {
    this.data = { …this.data, …data };
    this.render();
    }

    render() {
    this.container.innerHTML = `
    <div class="dashboard">
    <div class="stat-card">
    <div class="stat-value">${this.data.errorCount || 0}</div>
    <div class="stat-label">今日错误数</div>
    </div>

    <div class="stat-card">
    <div class="stat-value">${this.data.avgLoadTime || 0}ms</div>
    <div class="stat-label">平均加载时间</div>
    </div>

    <div class="stat-card">
    <div class="stat-value">${this.data.avgFps || 60}</div>
    <div class="stat-label">平均 FPS</div>
    </div>

    <div class="stat-card">
    <div class="stat-value">${this.data.dau || 0}</div>
    <div class="stat-label">日活用户</div>
    </div>
    </div>
    `;
    }
    }

    告警机制

    告警规则配置

    const alertRules = [
    {
    id: 'error_rate',
    name: '错误率告警',
    condition: (data) => data.errorRate > 1,
    threshold: 1,
    level: 'critical',
    message: '错误率超过 1%'
    },
    {
    id: 'slow_load',
    name: '加载缓慢告警',
    condition: (data) => data.avgLoadTime > 3000,
    threshold: 3000,
    level: 'warning',
    message: '平均加载时间超过 3 秒'
    },
    {
    id: 'fps_drop',
    name: 'FPS 下降告警',
    condition: (data) => data.avgFps < 30,
    threshold: 30,
    level: 'critical',
    message: '平均 FPS 低于 30'
    }
    ];

    // 检查告警
    function checkAlerts(data) {
    const triggeredAlerts = [];

    for (const rule of alertRules) {
    if (rule.condition(data)) {
    triggeredAlerts.push({
    …rule,
    timestamp: Date.now(),
    currentValue: getCurrentValue(data, rule)
    });
    }
    }

    return triggeredAlerts;
    }

    总结

    一个完整的前端监控体系应该包含:

  • 错误监控:捕获 JS 错误、资源错误、接口错误
  • 性能监控:页面加载时间、FPS、自定义指标
  • 用户行为:点击事件、页面浏览、表单提交
  • 数据上报:批量上报、离线缓存、重试机制
  • 数据可视化:仪表盘、趋势图、实时监控
  • 告警机制:多级别告警、通知渠道
  • 技术应当有温度,一个好的监控系统不仅能帮助开发者发现问题,更能帮助产品团队做出数据驱动的决策,最终提升用户体验。


    你在前端监控方面有什么经验?欢迎在评论区交流~

    赞(0)
    未经允许不得转载:171主机测评 » 前端监控体系建设:从异常捕获到性能分析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址