前端监控:拥有一双蜻蜓眼

为什么需要前端监控

通常,后端会配备日志功能以供查阅,这些日志详尽记录了各类接口调用的情况。然而,尽管后端日志对于追踪系统行为至关重要,前端页面却因其与用户有着"面对面"交互的特性,同样需要我们密切关注。具体来说,我们需要洞察用户在页面上的实际操作路径,监测前端在实际运行中是否遭遇问题,以及识别存在哪些潜在的优化空间,以确保前端体验的顺畅与高效。

操作路径收集

通过事件收集操作脚印

我们能够运用事件监听来捕获用户在页面上的 clickkeydown 等互动行为。然而,在此过程中,我们需意识到并非所有此类事件均具有分析价值。例如,用户对页面空白区域的无效点击,或随意敲击键盘产生的输入,本质上属于干扰数据,即"噪音"。这类"噪音"应当在数据收集阶段就被精准识别并剔除,以确保我们专注于分析真正有意义的用户交互信息。

typescript 复制代码
document.addEventListener('click', ({ target, type }) => {
    const tagName = target.tagName.toLowerCase();
    /**
     * 认为 'button', 'a', 'input' 标签,以及添加了 `cursor: pointer` 样式的标签是有效点击
     */
    if (['button', 'a', 'input'].includes(tagName) || getComputedStyle(target).cursor === 'pointer') {
        log('event', {
            text: target?.innerText,
            eventType: type,
            tagName,
            // 使用了 dom-selector-generator (npm) 生成元素 selector
            selector: selectorGenerator(target),
        });
    }
})

document.addEventListener('keydown', ({ target, type }) => {
    const tagName = target.tagName.toLowerCase();
    /**
     * 认为 'textarea', 'input' 标签是有效输入
     */
    if (['textarea', 'input'].includes(tagName)) {
        log('event', {
            text: target?.value,
            eventType: type,
            tagName,
            // 使用了 dom-selector-generator (npm) 生成元素 selector
            selector: selectorGenerator(target),
        });
    }
})

通过路由相关监听,收集用户路过哪处地标

要达成我们的目标,可以采取两种策略:

  1. 利用路由事件监听机制,实时监控每一次路由变更情况;
  2. 对涉及路由跳转的关键 API 进行"把守",确保对其调用的全程控制。
scss 复制代码
// callback 为通用页面跳转事件收集回调方法
// 页面进入
addEventListener('hashchange', callback);
addEventListener('popstate', callback);
const originPushState = history?.pushState;
if (!!originPushState) {
    history.pushState = function() {
        originPushState.apply(this, arguments);
        callback();
    }
}
const originReplaceState = history?.replaceState;
if (!!originReplaceState) {
    history.replaceState = function(state, _, url) {
        originReplaceState.apply(this, arguments);
        if (history.state === null && state?.idx === 0 && !url) return;
        callback();
    }
}

// 页面离开
addEventListener('beforeunload', callback);

页面性能 & 错误信息收集

白屏监控

无论是采用 ReactAngular 还是 Vue 框架开发,每个应用皆拥有一个顶层的根元素。即便在运用其他框架或采用原生页面编写方式时,我们同样可以设定这样一个根节点。这样一来,当页面完全加载完毕后,只需查验该根节点下是否存在有效内容,即可准确判断出页面是否正处于空白(白屏)状态。

ini 复制代码
const timer = setInterval(() => {
    if (document.readyState === 'complete') {
        const rootEl = document.querySelector('#app');
        if (!!rootEl) {
            if (!!rootEl.innerHTML) {
                clearInterval(timer);
            }
        }
        log('blank', {
            target: rootSelector,
        });
        clearInterval(timer);
    }
}, 1000);

脚本报错

我们能利用对 errorunhandledrejection 事件的监听,系统性地汇集JavaScript脚本运行时的错误信息。其中,error 事件负责捕捉常规的脚本错误,而 unhandledrejection 事件则专门用于收录那些未被 catch 处理的 Promise 异常,从而实现对两类不同来源报错的全面监测与记录。

arduino 复制代码
addEventListener('error', ev => {
    const {
        filename,
        error,
    } = ev;
    log('js-error', {
        filename,
        message: error?.message,
        stack: error?.stack,
    });
});

addEventListener('unhandledrejection', ev => {
    const { reason } = ev;
    log('unhandledrejection-error', {
        message: reason,
    });
})

前端性能异常收集

我们可借助 PerformanceObserver 这一 api ,通过为其接收到的性能指标设定阈值,来有效地识别并记录前端性能问题。如此一来,任何超出预设标准的性能表现都将被自动纳入监控范围,有助于我们及时发现并着手解决前端性能瓶颈。

详细见:developer.mozilla.org/en-US/docs/...

请求监控

我们能够通过对 fetchXMLHttpRequest 原生接口的全面"把守",精确追踪其发送请求及接收响应的各个环节,以此来搜集网络请求与响应的相关信息。

javascript 复制代码
function fetchHandle() {
    const originFetch = window.fetch;
    window.fetch = function (input: RequestInfo | URL, init?: RequestInit | undefined) {
        // 收集请求信息
        const startTime = new Date().getTime();
        return originFetch.apply(window, arguments).then(async response => {
            const duration = new Date().getTime() - startTime;
            // 收集正常响应信息
            return response;
        }).catch(err => {
            const duration = new Date().getTime() - startTime;
            // 收集异常响应信息
            throw err;
        });
    }
}

function XHRHandle() {
    if (typeof XMLHttpRequest !== 'function') return;
    const originXMLHttpRequest = window.XMLHttpRequest;
    const originOpen = originXMLHttpRequest.prototype.open;
    const originSend = originXMLHttpRequest.prototype.send;
    const originSetRequestHeader = originXMLHttpRequest.prototype.setRequestHeader;
    originXMLHttpRequest.prototype.open = function (method, url) {
        originOpen.apply(this, arguments);
        // 收集请求信息
    }
    originXMLHttpRequest.prototype.setRequestHeader = function (name, value) {
        originSetRequestHeader.apply(this, arguments);
        // 收集请求头信息
    }
    originXMLHttpRequest.prototype.send = function (body) {
        originSend.apply(this, arguments as any);
        const startTime = new Date().getTime();
        // 收集请求信息
        const onLoaded = () => {
            const duration = new Date().getTime() - startTime;
            // 收集正常响应信息
        }
        xhr.addEventListener('readystatechange', () => {
            if (xhr.readyState === 4) {
                onLoaded();
            }
        })
    }
}

收集资源加载失败信息

我们可以通过监听 error 事件,并对其抛出的事件对象进行类型检测类型检测,判断是否属于 ErrorEvent 即可判断是否存在资源加载失败的情况

javascript 复制代码
addEventListener('error', ev => {
    if (!(ev instanceof ErrorEvent)) {
        const target = (ev as Event).target as (HTMLScriptElement | HTMLLinkElement);
        const source = (target as HTMLScriptElement).src || (target as HTMLLinkElement).href;
        log('resource-error', {
            source,
            type: target.tagName,
        });
    }
}, true);

尝试实现

基于上述方案设计与编码实践,简单开发出了一款小工具,并配套设计了一个简洁的测试页面以验证其功能。以下是该工具的实际测试效果展示:

小工具开源分享

文档:github.com/shore1225/f...

提供了 CDN 和 npm 两种接入手段,用来达到对前端的监控

本文包含的插图和文案均通过通义(通义万象、通义千问)产品生成或加工

相关推荐
fishmemory7sec6 分钟前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec8 分钟前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
豆豆1 小时前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建
twins35202 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky2 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~2 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n03 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。3 小时前
案例-任务清单
前端·javascript·css
zqx_74 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架