错误捕获

在介绍监控 SDK 前,我们首先先来看我们日常开发中的错误捕获机制。

try…catch

  • 不能捕获语法错误
  • 比较消耗性能,尤其在 IE
  • 不能捕获异步错误
  • 不能冒泡

window.onerror

  • 可以捕获异步任务
  • 可以捕获语法错误
  • return true 时,会阻止报红
  • 不能捕获网络请求错误
  • 可以捕获 iframe

window.addEventListener(‘error’)

  • 可以捕获网络请求
  • 不能阻止浏览器报红
  • 不能捕获 promise

window.addEventListener(‘unhandledrejection’)

  • 可以捕获 promise 错误
  • 可以阻止默认处理(报红)

Performance.timing

Performance.timing,返回了 21 个只读属性,其中包含了页面加载的各种时间。
img

在写性能监控前,我们先来看基础的 api。

  • navigationStart:卸载页面后开始导航的时间。
  • redirectStart:开始重定向的时间。
  • redirectEnd:重定向结束时间。
  • fetchStart:http 开始读取文档时间。
  • domainLookupStart:域名查询开始时间。
  • domainLookupEnd:域名查询结束时间。
  • connectStart:http 开始请求服务器时间。
  • secureConnectionStart:开始握手时间。
  • connectEnd:连接建立时间。
  • requestStart:发起 http 的时间。
  • responseEnd:接受服务器字节完成的时间。
  • unloadEventStart:页面开始卸载时间。
  • unloadEventEnd:页面卸载完成时间。
  • domLoading:开始解析 DOM 时间。
  • domInteractive:DOM 解析完成时间。
  • domContentLoadedEventStart:DOMContentLoaded,开始执行脚本时间。
  • domContentLoadedEventEnd:所有脚本执行完成的时间。
  • domComplete:DOM 结构生成后时间。
  • loadEventStart:load 开始执行时间。
  • loadEventEnd:load 回调执行完成时间。

PerformanceObserver

MDN:用于监测性能度量事件,在浏览器的性能时间轴记录下一个新的 performance entries 的时候将会被通知 。当然,也可以使用上面的 preformance.getEntriesByType()方法来得到对应 type 的数据。

  • preformance.getEntriesByType,接受一个 PerformanceEntry.entryType 字符串,返回其对应类型的数据
performance.getEntriesByType(type);
// type: 'resource' | 'mark' | 'measure' | 'paint' | 'iframe/navigation'

接着,简单手写 SDK

主文件

async function Monitor(
    logUrl = 'http://duanxl.com/log',
    isPage = true,
    isError = true
) {
    // 是否需要上报
    if (isPage) {
        const PerForPage = await import('./perForPage');
        new PerForPage(logUrl);
    }
    // 捕获错误
    if (isError) {
        const Catch = await import('./catch');
        new Catch(logUrl);
    }
}

export default Monitor;

base.js 用于上报

class Base {
    constructor(logUrl, config) {
        this.logUrl = logUrl;
        this.config = config;
    }
    send() {
        if (navigator.sendBeacon) {
            navigator.sendBeacon(this.logUrl, this.config)
        } else {
            // new Image()
        }
    }
}
export default Base;

perForPage.js 性能监控

import Base from './base'
class PerForPage extends Base {
    constructor() {
        this._performance();
        super(this.logUrl, this.config);
        this.config = {};
    }
    _performance() {
        let timing = performance.timing;  //居然要废弃。。。将会有更强大的api来代替
        this.config.performance = {
            dns: timing.domainLookupEnd - timing.domainLookupStart,
            // 重定向耗时
            redirect: timing.redirectEnd - timing.redirectStart,
            dom: timing.domComplete - timing.domLoading,
            load: timing.loadEventEnd - timing.navigationStart,
            unload: timing.unloadEventEnd - timing.unloadEventStart,
            request: timing.responseEnd - timing.requestStart,
            whiteScreen: timing.domLoading - timing.navigationStart,
            // now time
            time: Date.now()
        }
        const observer = new PerformanceObserver((list) => {
            const lists = list.getEntries();
            this.config.performance.fp = lists[0].duration;
            this.config.performance.fcp = lists[0].duration;
            this.send(this.logUrl, this.config);

        });
        observer.observe({ entryTypes: ['resource', 'paint'] });
    }
}

export default PerForPage;

catch.js 错误收集

import Base from './base'
class Catch extends Base {
    constructor() {
        super(this.logUrl, this.config);
        this.config = {};
        this.catchError();
    }
    catchError() {
        window.onerror((error)=>{
            // ...
            return true;
        })
        window.addEventListener('unhandledrejection',()=>{
            // ...
            return true;
        })
        window.addEventListener('error',(e)=>{
            // ...
        },true)

    }
}

export default Catch;

xpath.js 录屏

let xpathArr = [];
// 录屏
window.addEventListener('click', (e) => {
    xpathArr.push(e.target.id ?? e.target.className);
    if (xpathArr.length >= 10) {
        navigator.sendBeacon('https://duanxl.com/xpath', xpathArr.join('->'));
        xpathArr = [];
    }
})
0