lcl-monitor-sdk
v1.1.1
Published
**尚在开发测试中**-"version": "1.1.1" 目标:可以采集和上报错误信息和性能指标(包括但不限于FCP、LCP),兼容至Chrome39+
Downloads
18
Readme
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-接口异常
通过重写
XMLHttpRequest
和fetch
的原生方法来实现重写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-urlencoded
、multipart/form-data
、text/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()