1.1.1 • Published 2 years ago

lcl-monitor-sdk v1.1.1

Weekly downloads
-
License
ISC
Repository
-
Last release
2 years ago

lcl-monitor-sdk

尚在开发测试中-"version": "1.1.1" 目标:可以采集和上报错误信息和性能指标(包括但不限于FCP、LCP),兼容至Chrome39+

这是一个前端异常和性能监控的采集上报SDK

异常监控:JS运行时异常;资源异常;未捕获的promise异常;xhr/fetch错误;白屏

性能监控:web-vitals三个核心指标和三个辅助指标;页面加载时间相关指标:tcp连接耗时、dom解析耗时、首次可交互时间等;卡顿;输入延时

使用方法

  • 基本用法
// npm引入方式
npm i lcl-monitor-sdk

import Monitor from 'lcl-monitor-sdk'

const monitor = new Monitor({
    requestUrl: "http://localhost:8080/monitor/",  // 上报接口地址,必填
    jsError: true,  // JS异常会上报到http://localhost:8080/monitor/JsError,webVitals会上报到http://localhost:8080/monitor/webVitals
    webVitals: true
})

// script引入方式
<script src="dist/index.js"></script>
<script type="text/javascript">
const monitor = new Monitor({
    requestUrl: "http://localhost:8080/monitor/", 
    jsError: true,
    webVitals: true
})
</script>
  • options 介绍
/**
 * @uuid 用户id,保留字段,用户行为统计中可能会用到
 * @requestUrl 上报接口地址
 * @reportTiming 数据上报时间点,默认为采集到数据即上报。目前仅可选'beforeunload',在页面卸载前统一上报
 * @isLog 开启后采集的数据在控制台打印,不上报
 * @jsError 是否开启js运行时异常、资源加载异常、未捕获的promise异常上报
 * @xhrAndFetch 是否开启xhr和fetch请求异常的上报
 * @blankScreen 是否开启白屏情况的上报
 * @blankScreenDuration 传入毫秒数自定义白屏认定的阈值
 * @loadTiming 是否开启tcp连接耗时、ttfb、dom解析耗时、首次可交互时间等页面加载时间的上报
 * @webVitals 是否开启谷歌的web-vitals性能监测指标上报,包括CLS、FID、FCP、LCP、TTFB、INP
 * @longTask 是否开启卡顿上报
 * @inputDelay 是否开启输入延时上报
 */
DefaultOptions:{
    uuid: string | undefined
    requestUrl: string | undefined
    reportTiming: string | 'auto'
    isLog: boolean | false
    jsError: boolean | false
    xhrAndFetch: boolean | false
    blankScreen: boolean | false
    blankScreenDuration: number | 3000
    loadTiming: boolean | false
    webVitals: boolean | false
    longTask: boolean | false
    inputDelay: boolean | false
}
  • 手动上报
const monitor = new Monitor({
    requestUrl: "http://localhost:8080/monitor/", 
    jsError: true
})

// 手动上报
/**
 * @data 请求体,对象格式
 * @type 自定义上报类型(即接口的二级地址,http://localhost:8080/monitor/type)
 */
monitor.manualReporting(data, type)

数据采集

异常采集

  • jsError-JS异常

    通过window.addEventListener('error')捕获JS运行时异常和资源加载异常,window.addEventListener('unhandledrejection')捕获未处理的promise reject异常

    window.addEventListener('error')能同时捕获JS运行时异常和资源加载异常,可以通过event.target && (event.target.src || event.target.href)来区分,为true说明是资源加载异常

    window.addEventListener('error', () => {
         if (event.target && (event.target.src || event.target.href)) {
             // 资源加载异常上报
         } else {
             // JS运行时异常上报
         }
    })
    window.addEventListener('unhandledrejection', () => {
    	// 未处理的promise reject异常上报
    })
  • xhrAndFetch-接口异常

    通过重写XMLHttpRequestfetch的原生方法来实现

    重写XMLHttpRequest

    if(!window.XMLHttpRequest) return
    let XMLHttpRequest = window.XMLHttpRequest
    let oldOpen = XMLHttpRequest.prototype.open
    XMLHttpRequest.prototype.open = function (method, url) {
        this.logData = {method, url}
        return oldOpen.apply(this, arguments)
    }
    let oldSend = XMLHttpRequest.prototype.send
    XMLHttpRequest.prototype.send = function (body) {
    	if (this.status > 0 && this.status < 400) return
        let handler = (type) => (event) => {
            // 上报xhr异常
        }
        this.addEventListener('load', handler('load'), false)
        this.addEventListener('error', handler('error'), false)
        this.addEventListener('abort', handler('abort'), false)
    }
    oldSend.apply(this, arguments)

    重写fetch

    if(!window.fetch) return
    let oldFetch = window.fetch
    window.fetch = function (requestInfo, requestInit) {
        return oldFetch.apply(this, arguments)
        .then(res => {
            if (res.status >= 400) { 
                // 上报fetch异常
            }
            return res
        })
        .catch(error => {
            // 上报fetch异常
            throw error
        })
    }
  • blankScreen-白屏

    方案实现优点缺点
    基于DOM的检测页面load完成后在页面中拿数个document.elementsFromPoint,拿数组的第一个即最内层元素,判断其是否为html、body、container等容器元素,是的话判定为空白点相对灵活,可传入要判定为空白容器的CSS选择器、自定义elementsFromPoint数量、设置空白点上报阈值等elementsFromPoint数量多的情况对页面性能有一定影响,兼容性较差
    基于Performance API页面load完成后数秒内没有FP对页面性能几乎没有影响有FP的情况下也可能是白屏
    基于MutationObserver页面load完成后数秒内有无DOM节点变化对页面性能几乎没有影响DOM节点无变化不一定代表白屏

    选择通过performance.getEntriesByName('first-paint')获取FP,如果页面加载完成三秒内没有FP,则认为出现白屏异常

    onload(function () {
        setTimeout(() => {
            const FP = performance.getEntriesByName('first-paint')[0]
            if (!FP) {
    			// 上报白屏异常
            }
        }, 3000)
    })

性能采集

  • loadTiming-页面加载时间相关指标

    最新标准应该使用performancePaintTiming API,但是为了兼容Chrome39+选择通过performance.timing API实现

    包括几个相对重要的页面加载时间相关指标:TCP连接耗时(connectEnd - connectStart)、网络请求耗时即TTFB(responseStart - requestStart)、Response响应耗时(responseEnd - responseStart)、DOM解析渲染耗时(loadEventStart - domLoading)、DOMContentLoaded事件回调耗时(domContentLoadedEventEnd - domContentLoadedEventStart)、首次可交互时间即TTI(domInteractive - fetchStart)、页面完全加载时间(loadEventStart - fetchStart)、onload事件回调耗时(loadEventEnd - loadEventStart)

    onload(function () {
        setTimeout(() => {  // onload三秒后再拿性能评价指标
            const {
                fetchStart,
                connectStart,
                connectEnd,
                requestStart,
                responseStart,
                responseEnd,
                domLoading,
                domInteractive,
                domContentLoadedEventStart,
                domContentLoadedEventEnd,
                domComplete,
                loadEventStart,
                loadEventEnd
            } = performance.timing
    		// 上报页面加载时间相关指标
        }, 3000);
    });
  • webVitals-谷歌性能指标

    包括CLS、FID、FCP、LCP、TTFB、INP六个指标,前三个即Core Web Vitals

    调用Chrome的web-vitals轮子

    const reportWebVitals = onPerfEntry => {
        if (onPerfEntry && onPerfEntry instanceof Function) {
            import('web-vitals').then(({onCLS, onFID, onFCP, onLCP, onTTFB, onINP}) => {
                onCLS(onPerfEntry)
                onFID(onPerfEntry)
                onFCP(onPerfEntry)
                onLCP(onPerfEntry)
                onTTFB(onPerfEntry)
                onINP(onPerfEntry)
            });
        }
    };
    
    reportWebVitals((metric) => {
        // 上报webVitals指标
    })
  • longTask-卡顿(无法兼容至Chrome39+)

    响应用户交互的响应时间如果大于100ms,用户就会感觉卡顿。浏览器的事件队列机制决定,要实现小于100毫秒的响应,应用必须在每50毫秒内将控制返回给主线程,页面整个生命周期中,主线程持续执行某一个任务的耗时大于50ms就会造成卡顿

    通过PerformanceLongTask检测卡顿,可以检测到浏览器内核主线程持续执行某一个任务超过50ms的情况:

    if (typeof PerformanceObserver !=='undefined') {
    	new PerformanceObserver((list) => {
    		list.getEntries().forEach(entry => {
                if (entry.duration > 50) {
                    // 拿到最近一次触发的输入事件
                    let lastEvent = getLastEvent()
                    // requestAnimationFrame回调函数会在绘制之前执行;requestIdleCallback是在绘制之后执行,在浏览器一帧的剩余空闲时间内执行
                    requestIdleCallback(() => {
                        // 上报卡顿情况
                    });
                }
            });
        }).observe({type: "longtask", buffered: true})
    } else {
    	console.log('当前浏览器不支持longTask')
    }
  • inputDelay-输入延时(无法兼容至Chrome39+)

    这里主要处理事件的响应速度:从用户操作触发事件到页面响应的耗时,通常要求小于100ms

    基于PerformanceEvent监听用户的输入(如click、touchstart、mousedown、keydown、mouseover等)到浏览器给出响应的延迟时间

    if (typeof PerformanceObserver !=='undefined') {
     	new PerformanceObserver((list) => {
            // 拿到最近一次触发的输入事件
        	let lastEvent = getLastEvent()
            let event = list.getEntries()[list.getEntries().length - 1]
            if (event.duration > 100) {
    			// 上报输入延时超过100ms的慢响应情况
            }
    	}).observe({type: "event", buffered: true})
    } else {
    	console.log('当前浏览器不支持inputDelay')
    }

数据上报

  • 上报时机

    常见上报时机有页面加载时、页面卸载或页面刷新时、SPA 路由切换时、页面多个 tab 切换时

    本项目目前仅支持页面加载时(默认)和页面卸载或页面刷新时(reportTiming: 'beforeunload'

  • 上报方式

    默认使用navigator.sendBeacon进行上报,如果浏览器不兼容则使用fetch进行上报(均为post请求)

    • navigator.sendBeacon

      只能发post请求,在页面卸载后不会取消请求,可以保证数据有效送达,不会阻塞页面的卸载或加载,恰好兼容到Chrome39+,且使用简单,支持跨域。但是不支持请求数据类型:Content-Type:application/json,只支持application/x-www-form-urlencodedmultipart/form-datatext/plain三种,本项目中使用multipart/form-data数据类型

      // sendBeacon会自动设置content-type为formData格式
      const formData = new FormData()
      Object.keys(logs).forEach((key) => {
          let value = logs[key]
          if (typeof value !== 'string') {
              // formData只能append string 或 Blob
              value = JSON.stringify(value)
          }
          formData.append(key, value)
      });
      navigator.sendBeacon(url, formData)
    • fetch

      通过设置keepalive: true也可以达到类似navigator.sendBeacon在页面卸载后不会取消请求的效果。但是很可惜,fetch兼容到Chome42+,只能作为备选上报方式

      fetch(url, {
          body,
          method: 'POST',
          keepalive: true,
          headers: {'Content-Type': "application/json"}
      }).then()
1.1.1

2 years ago

1.1.0

2 years ago

1.0.9

2 years ago

1.0.8

2 years ago

1.0.7

2 years ago

1.0.6

2 years ago

1.0.5

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago