这是我参与「第四届青训营」笔记创作活动的第16天
为什么要做前端监控系统
- 更快地发现问题
- 做产品决策依据
- 提升前端开发的技术深度和广度
- 为业务扩展提供更多可能性
解决了什么问题
首先,用户在使用产品过程中很可能会发生一些问题:
- 白屏
- 无响应
- 卡顿
- 服务异常
- bug无法复现
- 等等
面对这些问题,我们需要先知道用户在使用时到底发生了什么,我们不可能一直去依赖用户的主动反馈,那显然是远远不够的,于是我们可以想到:
- 收集异常:解决异常、兼容性等问题
- 收集性能:解决慢查询、慢加载等问题
- 收集接口:发现接口错误、打通服务端监控
- 收集多方面辅助信息,综合多方面分析,提高产品价值
为了实现这些,我们需要前端监控系统的存在,可以让我们在第一时间收集数据、处理数据、存储数据、查询数据。
基本构成
- 监控上报SDK:数据收集
- 服务端:数据处理
- 数据库:数据建模存储
- 管理台:数据可视化展示
SDK开发文档
埋点SDK
- sdk收集项目的关键数据,向外暴露上报埋点的接口,监听和收集过程开发人员无感知。
- 使得业务开发人员只需关注事件标识、业务属性等。
- 兼顾无痕埋点优点和代码埋点的优势。
收集数据分析:
稳定性指标
-
js错误
- js运行错误
- promise错误
- js脚本异常
-
资源加载异常
-
vue错误
-
白屏异常
用户体验指标/性能
-
timing:
- DNS解析时间
- TCP连接时间
- domReady,DOM阶段渲染耗时
- 响应读取时间
- ttft:第一个首字节响应的时间
-
paint
- FP:白屏时间
- FCP:灰屏时间
- FMP:页面首次有效绘制时间,FMP >= FCP
- LCP:页面最大内容渲染时间
- TTFB:发出请求到应答请求的第一个字节的耗时
- FID:用户首次交互操作的延时时间
- TTL:页面加载到页面处于完全可交互状态所花费的时间
用户行为指标
- PV:页面浏览量
- UV:24小时内访问网站的独立用户数(IP数目)
- 页面停留时间: load事件-beforeunload事件
接口监控
- http请求是否异常、请求路径、请求持续时间等
上报格式设计:
稳定性指标(stability)
js运行错误
errorType: "jsError"//错误类型中的:JS执行错误
filename: "http://localhost:8080/"//报错文件
kind: "stability"//监控指标的大类:稳定性指标
message: "Uncaught TypeError: Cannot set properties of undefined (setting 'error')"//报错信息
position: "22:28"//报错具体位置 行:列
selector: "html body div#container div.content input"//最后一个操作的元素
stack: "errorClick (http://localhost:8080/:22:28)^HTMLInputElement.onclick (http://localhost:8080/:13:66)"//堆栈信息
timestamp: "1659340310679"//时间戳
title: "Front-end monitor SDK"//文件名
type: "error"////小类型 这是一个错误
url: "http://localhost:8080/"
userAgent: "chrome"//浏览器
//其他:用户id/token
js脚本错误
errorType: "resourseError"
filename: "http://127.0.0.1:5500/dist/texterror.js"
hostname: "127.0.0.1"
kind: "stability"
tagName: "SCRIPT"
timestamp: "1659870028382"
title: "Front-end monitor SDK"
type: "error"
url: "http://127.0.0.1:5500/dist/index.html"
userAgent: "chrome"
promise错误
errorType: "promiseError"
filename: "http://localhost:8080/"
kind: "stability"
message: "Cannot set properties of undefined (setting 'error')"
position: "28:30"
selector: "html body div#container div.content input"
stack: "http://localhost:8080/:28:30^new Promise (<anonymous>)^promiseErrorClick (http://localhost:8080/:27:7)^HTMLInputElement.onclick (http://localhost:8080/:15:80)"
timestamp: "1659342026871"
title: "Front-end monitor SDK"
type: "error"
url: "http://localhost:8080/"
userAgent: "chrome"
资源加载异常
errorType: "resourseError"
filename: "http://127.0.0.1:5500/dist/testimg.png"
hostname: "127.0.0.1"
kind: "stability"
tagName: "IMG"
timestamp: "1659870028389"
title: "Front-end monitor SDK"
type: "error"
url: "http://127.0.0.1:5500/dist/index.html"
userAgent: "chrome"
vue2、vue3错误
componentName: "<Cate> at src/components/home/goods/cate/Cate.vue"//错误组件名
errorType: "vueError"//错误类型:vueError
hook: "created hook"//vue错误所在的生命周期钩子
hostname: "localhost"
kind: "stability"
timestamp: "1660047596681"
title: "dev -电商后台管理系统"
type: "error"
url: "http://localhost:8080/categories"//url地址
userAgent: "chrome"
value: "this.promiseErrorClick is not a function"//错误信息
接口异常
duration: "12"//请求持续时间
eventType: "load"//
kind: "stability"
params: ""//参数
pathname: "/success"//接口
response: ""//响应值
status: "404-Not Found"//状态码
timestamp: "1659340032798"
title: "Front-end monitor SDK"
type: "xhr"
url: "http://localhost:8080/"
userAgent: "chrome"
//其他:用户id/token
白屏异常
emptyPoints: "18"//空白元素个数
kind: "stability"
screen: "1230X692"//屏幕大小
selector: "body"
timestamp: "1659423294652"
title: "Front-end monitor SDK"
type: "blank"
url: "http://localhost:8080/"
userAgent: "chrome"
viewPoint: "205X550"//窗口大小
用户体验指标/性能监控(experience)
timing
connectTime: "0"//TCP连接时间
dnsTime: "0"//DNS解析时间
domContentLoadedTime: "1001"// DOMContentLoaded事件时间
domReadyTime: "1096"//domReady,DOM阶段渲染耗时
kind: "experience"
loadTime: "1117"// 页面完整的加载时间
parseDOMTime: "1104"// DOM 解析时间
responseTime: "1"// 响应读取时间
timeToInteractive: "39"// 首次可交互时间,白屏时间
timestamp: "1659538575589"
title: "Front-end monitor SDK"
ttfbTime: "2"// 第一个首字节响应的时间
type: "timing"
url: "http://localhost:8080/"
userAgent: "chrome"
paint
firstContentfulPaint: "1747"//fcp
firstPaint: "1747"//fp
kind: "experience"
largestContentfulPaint: "1747"//lcp
timestamp: "1659617273062"
title: "Front-end monitor SDK"
type: "paint"
url: "http://localhost:8080/"
userAgent: "chrome"
firstInputDelay
duration: "32"//处理耗时
inputDelay: "3.4000000953674316"//处理延迟时间
kind: "experience"// 用户体验指标
selector: "html body div#container div.content input"//操作元素
startTime: "21075.900000095367"//开始时间
timestamp: "1659617814260"
title: "Front-end monitor SDK"
type: "firstInputDelay"//首次输入延迟
url: "http://localhost:8080/"
userAgent: "chrome"
用户行为指标(behavior)
PV
effectiveType: "3g"//网络类型
entryType: "reload"//用户来路方式:navigate / reload / back_forward / reserved
hostname: "localhost"//主机的域名
kind: "behavior"//监控指标的大类:用户行为
markUv: "W5EMXE1JJG1659697134693"//UV标志
pathname: "/"//当前页面的路径和文件名
referrer: "http://localhost:8080/"//用户来路地址
rtt: "300"//往返时延
screen: "1230X692"
timestamp: "1659710224318"
title: "Front-end monitor SDK"
type: "pv"//小类型 pv
url: "http://localhost:8080/"
userAgent: "chrome"
UV
需要后端处理
服务端判断上报的 PV所属的IP 或者根据markUv字段。
如果是当天的第一次上报时,就给它记录一次 UV
markUv`:给每个用户每天随机生成的一个UV字符串,存储在`localstorage
页面停留时间
kind:"behavior"//用户行为指标
userAgent:"chrome"
title:"Front-end monitor SDK"
type:"residentTime"//页面停留时间
url:"http://localhost:8080/"
hostname:"localhost"
durations:"[{"startTime":1659796134482,"dulation":8791,"endTime":1659796143273,"pathname":"/"}]"//用户从进入页面到离开页面之间,各个路由的停留时间,以数组形式返回
timestamp:"1659796143273"
采集方式
主要用到的API
下图是 SDK 主要使用的 Web API ,通过这几个 API 我们就能分别获取到:页面性能信息、资源性能信息、 ajax 信息、错误信息
稳定性指标
jsError:
主要使用unhandledrejection、error 以及框架自带的监听函数
error可以收集到js运行错误与资源加载异常。
unhandledrejection可以收集promise异常
由于框架中的异常会被框架所捕获,所以在js中不能收集到框架中出现的错误,所以需要对框架的监听函数进行处理
接口异常:
主要对xmlHttpRequest的send和open方法进行重写
由于浏览器并没有提供一个统一的 API 使我们能够收集到 ajax 请求和响应数据,并且不管我们是用 axois 还是使用其他的 http 请求库,他们都是基于xmlHttpRequest 实现的。
因此只能通过重写xmlHttpRequest ,并在对应的函数和逻辑中插入自定义代码,来达到收集的目的。
此处还对上报请求,做了额外处理,防止由于上报请求而导致上报死循环。
白屏异常:
主要使用document.elementFromPoint采集以页面中心为原点,横向x轴和纵向y轴取18个观察点,判断是否有dom元素渲染,来判断是否存在白屏异常
用户体验指标/性能监控
主要使用了Performance API
dns解析时间、TCP连接时间、响应读取时间、页面完全加载时间、domReady、TTFB等性能相关数据:通过 performance.timing 获取
LCP:通过 proformanceObserver获取。
FP、FCP、FID等数据:通过 performance.getEntriesByName获取
用户行为指标
因为考虑到SPA应用,所以对pushState和replaceState进行重写,并添加了监听事件,获取到用户的进入和离开。
页面停留时间:在页面beforeunload触发时,进行停留时间的上报
PV/UV:对于UV,在sdk部分给每个用户每天进入被监控系统的行为,在本地存一个唯一的UV标识符和UVtime标识符,后端可直接根据UV字段来判断该用户是否为今天第一次进入网站,来进行UV数据的统计。