一文搞懂得物前端监控

2023-12-13 13:28

本文主要是介绍一文搞懂得物前端监控,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、背景

得物的服务端监控是比较全面和有效的,除了上报原始日志数据,还通过数据分析制定线上告警机制,调用链路分析,而针对前端项目这一块,还是不够全面的。对前端线上问题感应不及时,靠人肉发现,没有告警机制等问题,所以就有个前端监控这个项目。前端监控也确实很有必要,我们需要对线上的页面有个全面的把控,而至于怎么做监控,做数据上报,以及数据分析,如何针对监控数据分析出有用的核心链路的告警等也能有个全面的认识。本文主要是介绍得物针对监控做了哪些事情以及对前端底层监控手段做个总结。

二、监控类型

前端监控的范围很广,如监控性能,监控异常,监控告警等一系列的维度来确保我们的页面和功能是正常的,在出现问题时研发可以及时做出响应,及时追踪,定位问题。

性能监控

目前前端平台在性能监控和异常监控是分开的,性能监控有专门 SDK,去做上报和分析,联合得物 App 端上一起做了性能优化和数据分析,这块是独立的。主要包括性能的上报,以及性能的优化手段。下图是前端性能监控方面的数据展示效果。

7.png

总体来说性能监控是做的比较大的,而且监控对提高页面的秒开是有实际优化指导意义的,离线包、预请求等都是优化手段。

异常告警监控

针对异常监控也有单独的平台和 SDK 去承接和上报,下图来展示收集一些异常信息,对异常信息进行分类,再通过设置告警机制来通知开发人员及时发现问题:

8.png

这些错误类的信息不一定是我们都需要关注的,有些疑难杂症,但是有不影响页面展示和功能的报错,也是可以忽略的,要知道不是所有的错误都能被解决的,这个时候我们可以只关注那些影响我们页面核心功能的部分,针对这部分做一个告警配置,例如:

9.png

针对告警信息,再进一步对错误进行分析,找到能解决的问题,达到对页面稳定性的把控。

日常巡检监控

还有一种比较特殊的场景,针对运营活动做的营销会场,在各个配置的坑位去做巡检,提前发现会场是否正常,有没有白屏、API 异常等提前发现,然后联系相应的人去人工处理。这种监控模式对于要投放出去的页面做提前检查是很有效果的,事实证明也确实如此,提前避免了很多线上问题,很厉害。

5.png

三、监控知识梳理

上面几个大类型的监控都是前端同学在监控方面做的努力和成果,涉及到的知识点也很多,同样对于业务的把控和理解也很深入,很值得学习并了解,后面的内容就是针对监控做个梳理总结,了解下大致的实现方式。

前端监控的必要性

用户在访问页面的时候大致会经历下面的阶段:

  • 向服务端请求获取静态资源;
  • 浏览器加载资源;
  • 资源加载成功之后页面渲染继续运行。

这些阶段都有报错的可能,而前端要做的就是监控后面这阶段:资源加载和页面交互。

做前端监控也有很多其他好处, 例如:

  • 第一时间上报异常,解决问题;
  • 能够比较完整的重现问题用户的操作全流程路径,方便开发者复现问题,定位问题;
  • 针对页面的 PV、UV 等信息可以为产品和运营做推广决策提供数据依据。

前端监控是很有必要的,通过监控,我们能在线上应用异常时,第一时间收到反馈,并及时止损。对业务的发展是有正向作用的。

前端监控目标

保证稳定性(错误监控)

错误监控包括 JavaScript 代码错误、Promsie 错误、接口(XHR,Fetch)错误、资源加载错误(Script,Link等)等,这些错误大多会导致页面功能异常甚至白屏。

提升用户体验(性能监控)

性能监控包括页面的加载时间、接口响应时间等,侧面反应了用户体验的好坏。

  • 加载时间:页面运行时各个阶段的加载时间;
  • TTFB(Time To First Byte)(首字节时间):浏览器发起第一个请求到数据返回第一个字节所消耗的时间,这个时间包含了网络请求时间、后端处理时间;
  • FP(First Paint)(首次绘制):首次绘制包括了任何用户自定义的背景绘制,它是将第一个像素点绘制到屏幕的时刻;
  • FCP(First Content Paint)(首次内容绘制):首次内容绘制是浏览器将第一个 DOM 渲染到屏幕的时间,可以是任何文本、图像、SVG 等的时间;
  • FMP(First Meaningful paint)(首次有意义绘制):首次有意义绘制是页面可用性的量度标准;
  • LCP(Largest Contentful Paint):视窗内最大的图片或者文本渲染的时间,当最大的内容块渲染完的时候,基本上主内容都加载完了,与现有的页面加载指标相比,与用户体验的相关性更好;
  • FID(First Input Delay)(首次输入延迟):用户首次和页面交互到页面响应交互的时间;
  • 卡顿:指超过 50ms 的长任务,具体的指标可以根据页面的内容进行调节,一般 50ms 人眼就能感觉到卡顿。

针对业务进行统计

  • PV:Page View 即页面浏览量或点击量;
  • UV:指访问某个站点的不同 IP 地址的人数;
  • 页面的停留时间:用户在每一个页面的停留时间。

前端监控的流程

  • 前端埋点(通过 SDK 给页面的 DOM 都加上标记)
  • 数据上报(收集,存储)
  • 分析和计算(将采集到的数据进行加工汇总)
  • 可视化展示(按照纬度将数据展示)
  • 监控报警(发现异常后按一定的条件触发报警)

前端埋点方案

代码埋点

代码埋点,就是项目中引入埋点 SDK,手动在业务代码中标记,触发埋点事件进行上报。比如页面中的某一个模块的点击事件,会在点击事件的监听中加入触发埋点的代码 this.$track('事件名', { 需要上传的业务数据 }),将数据上报到服务器端。

  • 优点:能够在任何时刻,更精确的发送需要的数据信息,上报数据更灵活;
  • 缺点:工作量大,代码侵入太强,过于耦合业务代码,一次埋点的更改就要引起发版之类的操作。

这个方案也是我们实际项目中现有的方案。

可视化埋点

通过可视化交互的手段,代替代码埋点,可以新建、编辑、修改埋点。在组件和页面的维度进行埋点的设计。

将业务代码和埋点代码分离,提供一个可视化交互的页面,输入为业务代码,通过这个可视化系统,可以在业务代码中自定义的增加埋点事件,最后输出的代码耦合了业务代码和埋点代码。

这个方案是可以解决第一种代码埋点的痛点,也是我们目前正准备做的方案。

无痕埋点

前端的任意一个事件都被绑定一个标识,所有的事件都被记录下来,通过定期上传记录文件,配合文件解析,解析出来我们想要的数据,并生成可视化报告。

无痕埋点的优点是采集全量数据,不会出现漏埋和误埋等现象。缺点是给数据传输和服务器增加压力,也无法灵活定制数据结构。针对业务数据的准确性不高。

监控脚本

日志存储

前端的埋点上报需要存储起来,这个可以使用阿里云的日志服务,不需要投入开发就可以采集。

新建一个项目比如:xxx-monitor

新建一个存储日志,根据阿里云的要求发起请求,携带需要上报的数据:

http://${project}.${host}/logstores/${logStore}/track

1.png

代码中调用 Track 上报日志:

日志的上报可以封装成公共的调用方式, monitor/utils/里面放所有的工具方法;

tracker.js 的实现就是按照阿里云的上报格式发送请求,并带上处理好的需要上报的业务数据即可,下面的都是固定的,在日志服务建好:

2.jpeg

实现一个 Tracker 类导出类的实例即可,这样在监控的核心代码中直接调用 tracker.send(data),核心实现代码如下:

// monitor/utils/get/track.js
...
class SendTrackLoger {constructor() {this.url = `http://${project}.${host}/logstores/${logStore}/track`this.xhr = new XMLHttpRequest()}send(data = {}, callback) {const logData = {...logData}for(let key in logs) {if (typeof logs[key] === 'number') {logs[key] = `${logs[key]}` // 这是阿里云的要求,字段不能是数字类型}}let body = JSON.stringify({__logs__: [logs]})this.xhr.open('POST', this.url, true)this.xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')this.xhr.setRequestHeader('x-log-apiversion', '0.6.0')this.xhr.setRequestHeader('x-log-bodyrawsize', body.length)this.xhr.onload = function() {if (this.status >= 200 && this.status <= 300 || this.status === 304) {callback && callback()}}this.xhr.onerror = function(error) {console.log(error)}this.xhr.send(body)}
}export default new SendTrackLoger()

这里展示的是自定义要上报的数据字段:

14.png

监控错误

前端需要监控的错误有两类:

  • Javascript 错误(JS 错误,Promise 异常)
  • 监听 Error 错误(资源加载错误)

脚本实现

新建一个 fronend-monitor 项目,这个项目就相当于我们的工程项目,监控的核心实现可以写到项目里面,也可以抽成 SDK 的形式 Import 引入进来,这里先写到项目中。

webpack.config.js 用来打包项目,做接口数据 Mock,测试 XHR 请求监控接口错误等。

const path = require('path')
const HtmlWebpackPlugin = xxxmodule.exports = {mode: 'development',context: process.cwd(),entry:'./src/index.js',output: {path: path.resolve(__dirname, 'dist'),filename: 'monitor.js'},devServer: {contentBase: path.resolve(__dirname, 'dist'),before(router) {router.get('/success', function(req, res) {res.json({ id: 1 })})router.post('/error', function(req, res) {res.sendStatus(500)})},},module: {},plugins: [new HtmlWebpackPlugin({template: './src/index.html',inject: "head"})]
}

新建一个 src/index.html 在这个里面写一些问题代码,然后测试监控的错误捕获。

// src/index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>monitor</title>
</head>
<body><input id="jsErrorBtn" type="button" value="js 代码错误" onclick="btnClick()" /><input id="promiseErrorBtn" type="button" value="promise 错误" onclick="promiseClick()" /><input id="successBtn" type="button" value="成功 ajax 请求" onclick="successAjax()" /><input id="errorBtn" type="button" value="失败 ajax 请求" onclick="errorAjax()" /><script>function btnClick() {window.goods.type = 2}function promiseClick() {new Promise((resolve, reject) => {resolve(1)}, () => {console.log(123)})}function successAjax() {var xhr = new XMLHttpRequest()xhr.open('GET', '/success',  true)xhr.responseType = 'json'xhr.onload = function () {console.log(xhr.response)}xhr.send()}function errorAjax() {var xhr = new XMLHttpRequest()xhr.open('POST', '/error',  true)xhr.responseType = 'json'xhr.onload = function() {console.log(xhr.response)}xhr.onerror = function(err) {console.log(err)}xhr.send('name=123')}
</script>
</body>
</html>

上报未捕获的 Javascript 错误

Javascript 错误分为 2 种:语法错误,资源家加载错误,这些错误都会被window.addEventListener('error', function(event) {})捕获,来判断是否是资源加载错误。

window.addEventListener('error', function(event) {// 如果 target 是script link 等资源if (event.target && (event.target.src || event.target.href)) {const element = getElement(event.target || event.path)tracker.send({...title: document.title,url: location.href,timestamp: event.timeStamp,userAgent: navigator.userAgent,type: 'resourceError',...})} else {tracker.send({...title: document.title,url: location.href,timestamp: event.timeStamp,userAgent: navigator.userAgent,type: 'jsError',...})}}, true)

代码中未被捕获的 Promise 错误,要监听 unhandledrejection 事件 window.addEventListener('unhandledrejection', function(event) {})。

// 监听未捕获的 promise 错误window.addEventListener('unhandledrejection', function(event) {// PromiseRejectionEventlet message = ''let stack = ''const reason =  event.reasonlet filename = ''let lineno = ''let colno = ''if (reason) {message = reason.messagestack = reason.stackconst match = stack.match(/\s+at\s+(.+):(\d+):(\d+).+/)filename = match[1]lineno = match[2]colno = match[3]}tracker.send({...title: document.title,url: location.href,timestamp: event.timeStamp,userAgent: navigator.userAgent,type: 'promiseError',...})}, true)

接口异常上报

接口异常上报主要是拦截请求,拦截 XMLHttpRequest 对象,改写 XHR 的 Open 和 Send 方法,将需要上报的数据发到阿里云存储,监听 Load,Error,Abort 事件,上报数据:

// src/monitor/lib/xhr.js
import tracker from '../utils/tracker'export default function injectXHR() {// 获取 window 上的 XMLHttpRequest 对象const XMLHttpRequest = window.XMLHttpRequest// 保存旧的open, send函数const prevOpen = XMLHttpRequest.prototype.openconst prevSend = XMLHttpRequest.prototype.send// 不可使用箭头函数,不然会找不到 this 实例XMLHttpRequest.prototype.open = function (method, url, async, username, password) {// 重写open,拦截请求// 不拦截 track 本身以及 socket, 直接放行if (!url.match(/logstores/) && !url.match(/sockjs/)) {this.logData = { method, url, async, username, password }}return prevOpen.apply(this, arguments)}XMLHttpRequest.prototype.send = function (body) {// 重写 send,拦截有 logData 的请求,获取 body 参数if (this.logData) {this.logData.body = bodylet startTime = Date.now()function handler(type) {return function (event) {// event: ProgressEventlet duration = Date.now() - startTimelet status = this.statuslet statusText = this.statusTextconsole.log(event)tracker.send({type: 'xhr',eventType: type,pathname: this.logData.url,status: `${status} ${statusText}`,duration: `${duration}`, // 接口响应时长response: this.response ? JSON.stringify(this.response) : '',params: body || '',})}}this.addEventListener('load', handler('load'), false)this.addEventListener('error', handler('error'), false)this.addEventListener('abort', handler('abort'), false)}return prevSend.apply(this, arguments)}
}

监控白屏

白屏就是页面上什么东西也没有,在页面加载完成之后,如果页面上的空白点很多,就说明页面是白屏的,需要上报,这个上报的时机是:document.readyState === 'complete' 表示文档和所有的子资源已完成加载,表示load(window.addEventListener('load')状态事件即将被触发。

document.readyState 有三个值:loading(document正在加载),interactive(可交互,表示正在加载的状态结束,但是图像,样式和框架之类的子资源仍在加载),complete 就是完成,所以监控白屏需要在文档都加载完成的情况下触发。

// src/monitor/utils/onload.js
export function onload(callback) {if (document.readyState === 'complete') {callback()} else {window.addEventListener('onload', callback)}
}

监控白屏的思路主要是:可以将可视区域中心点作为坐标轴的中心,在X、Y轴上各分 10 个点,找出这个 20 个坐标点上最上层的 DOM 元素,如过这些元素是包裹元素,空白点数就加一,包裹元素可以自定义比如 Html Body App Root Container Content 等,空白点数大于 0 就上报白屏日志。

export default function computedBlankScreen() {// 包裹玉元素列表const wrapperSelectors = ['body', 'html', '#root', '#App']// 空白节点的个数let emptyPoints = 0// 判断20个点处的元素是否是包裹元素function isWrapper(element) {const selector = getSelector(element)console.log(selector)if (wrapperSelectors.indexOf(selector) >= 0) { // 表示是在包裹元素里面,空白点就要加一emptyPoints++}}onload(function() {let xElements, yElementsfor (let i = 0; i <=9; i++) {xElements = document.elementFromPoint(window.innerWidth * i / 10, window.innerHeight / 2)yElements = document.elementFromPoint(window.innerHeight * i / 10, window.innerWidth / 2)// document.elementFromPoint 返回的是某一个坐标点的由到外的html元素的集合isWrapper(xElements[0])isWrapper(yElements[0])}if (emptyPoints >= 0) {let centerPoint = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2)tracker.send()}})
}

监控卡顿

用户交互的响应时间如果大于某一个时间,用户就会感觉卡顿。可以定一个时间比如 100 毫秒,就代表响应时间长,会卡顿。

PerformanceObserver 构造函数使用给定的观察者 Callback 生成新的PerformanceObserver 对象,当通过 Observe() 方法注册条目类型(需要监控的类型)的性能条目被记录下来时,会调用该观察者回调。

所以可以 new PerformanceObserver 来监控 longTask,监控的资源加载如果超过 100 毫秒就表示卡顿,可以浏览器空闲(requestIdleCallback)的时候上报数据。

....
export default function longTask() {new PerformanceObserver(function(list) {list.getEntries().forEach(function(entry) {if (entry.duration > 100) {// 浏览器空闲的时候上报requestIdleCallback(() => {tracker.send({type: 'longTask',eventType: lastEvent.type,startTime: formatTime(entry.startTime),duration: formatTime(entry.duration),});});}})}).observe({ entryTypes: ['longtask']})
}

性能指标

PerformanceObserver.observe 方法用于观察传入的参数中指定的性能条目类型的集合。当记录一个指定类型的性能条目时,性能监测对象的回调函数将会被调用。performance.timing 记录了从输入 URL 到页面加载完成的所有的时间,从这些字段中可以提取对对页面性能的监控,通过分析这些指标来优化页面的体验,比如统计 FMP、LCP 等,具体可以查看 MDN。

统计pv (页面的停留时间)

navigator.connection 对象获取网络连接的信息:effectiveType(网络类型),RTT(估算饿往返时间)等,还能通过监听 window.addEventListener('unload')事件计算用户在页面的停留时间。

import tracker from '../util/tracker';
export function pv() {var connection = navigator.connection;tracker.send({type: 'pv',networkType: connection.effectiveType,  // 网络类型rtt: connection.rtt, // 往返时间screen: `${window.screen.width}x${window.screen.height}` // 设备分辨率});let startTime = Date.now();window.addEventListener('unload', () => {let stayTime = Date.now() - startTime; // 页面停留时间tracker.send({type: 'stayTime',stayTime});}, false);
}

四、总结

前端监控是一个成熟业务线的标配,目前最多的场景是监控 JS 错误,接口请求和性能优化,然后根据日志信息进行分析分类的可视化展示,在发生异常的时候通知到相应的业务开发,监控的性能指标给页面的体验优化提供数据对比和优化的方向。

参考文章:

https://juejin.cn/post/6939703198739333127

https://wicg.github.io/largest-contentful-paint/

https://developer.mozilla.org/zh-CN/docs/Web/API/PerformanceObserver/PerformanceObserver

https://developer.mozilla.org/zh-CN/docs/Web/API/Long_Tasks_API

https://developer.mozilla.org/zh-CN/docs/Web/API/PerformanceTiming

https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator

*文/Zyqy

本文属得物技术原创,更多精彩文章请看:得物技术官网

未经得物技术许可严禁转载,否则依法追究法律责任!

这篇关于一文搞懂得物前端监控的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/488626

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用@CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略,该策略要求请求的域名、协议和端口必须与提供资源的服务相同。如果不相同,则需要服务器显式地允许这种跨域请求。一般在springbo

HTML提交表单给python

python 代码 from flask import Flask, request, render_template, redirect, url_forapp = Flask(__name__)@app.route('/')def form():# 渲染表单页面return render_template('./index.html')@app.route('/submit_form',