前端监控系统设计

前端异常监控汇总

一.为什么需要做前端监控

通过对客户端环境信息以及用户行为信息收集
在研发侧,能够对系统的运行情况做异常报警,快速定位异常问题,确保线上服务能够正常运行
在用户端,能够通过对用户行为进行分析,结合定制的指标,去衡量产品功能的使用率、服务性能、用户行为偏好等,为用户提供更好的产品体验
在产品侧,能够根据用户行为偏好,驱动产品迭代优化; 在运营侧,能够确认运营活动以及广告投放的效益

二.异常错误原因分类

img

错误类型

1.ECMAScript 2015中定义的错误构造函数
    EvalError eval错误
    RangeError 范围错误 当一个值不在其所允许的范围或者集合
    ReferenceError 引用错误
    TypeError 类型错误
    URIError URI错误
    SyntaxError 语法错误
    Error 通用错误
2.最新的DOM规范定义的错误类型集
    IndexSizeError 索引不在允许范围内
    HierarchyRequestError 节点树层次结构是不正确的
    WrongDocumentError 对象是错误的
    InvalidCharacterError 字符串包含无效字符
    NoModificationAllowedError 对象不能被修改
    NotFoundError 对象不能再这里被找到
    NotSupportedError 不支持的操作
    InvalidStateError 对象是一个无效的状态
    SyntaxError 字符串不匹配预期模式
    InvalidModificationError 对象不能以这种方式被修改
    NamespaceError 操作在xml命名空间内是不被允许的
    InvalidAccessError 对象不支持这种操作或参数
    TypeMismatchError 对象的类型不匹配预期的类型
    SecurityError 此操作是不安全的
    NetworkError 发生网络错误
    AbortError 操作被终止
    URLMismatchError 给定的URL不匹配另一个URL
    QuotaExceededError 已经超过给定配额
    TimeoutError 操作超时
    InvalidNodeTypeError 这个操作的 节点或节点祖先 是不正确的
    DataCloneError 对象不能克隆

三.异常监控方式

try…catch

在业务代码中对单个代码块进行包裹,或在逻辑流程中打点,实现有针对性的异常捕获:
专门写一个函数来收集异常信息,在异常发生时,调用该函数
处理异常的能力有限,只能捕获捉到运行时非异步错误,对于语法错误(syntaxError)和异步错误就显得无能为力,捕捉不到

    try {
        error    // 未定义变量
    } catch(e) {
        console.log('我知道错误了');
        console.log(e);// ReferenceError: error is not defined
    }finally{
       console.log("finally") // finally用于关闭资源等后续操作
   }

window.onerror
/**

  • 捕获javascript执行异常
  • @param {String} message 错误信息
  • @param {String} source 出错文件
  • @param {Number} lineno 行号
  • @param {Number} colno 列号
  • @param {Object} error Error对象(对象)
    */
    window.onerror = function (msg, url, row, col, error) {
        console.log('我知道错误了');
        console.log({
            msg,  url,  row, col, error
        })
        return true;
    };
    error;
// 异步错误
    window.onerror = function (msg, url, row, col, error) {
        console.log('我知道异步错误了');
        console.log({
            msg, url, row, col, error
        })
        return true;
    };
    setTimeout(() => {
        error;
    });

捕获异常能力比 try-catch 稍微强点,无论是异步还是非异步错误,onerror 都能捕获到运行时错误
onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示 Uncaught ReferenceError : xxxxx
在这里插入图片描述

对于 onerror 这种全局捕获,最好写在所有 JS 脚本的前面,因为你无法保证你写的代码是否出错,如果写在后面,一旦发生错误的话是不会被 onerror 捕获到的
无法捕获到网络异常的错误和不能捕获带有src的标签元素的加载错误

捕获资源加载异常

  1. 为捕获状态时(第三个参数为true)能捕获到js执行错误,也能捕获带有src的标签元素的加载错误。
    为冒泡状态时(第三个参数为false)能捕获到js执行错误,不能捕获带有src的标签元素的加载错误。
  2. 参数返回1个值,异常事件,错误信息都在里面
  3. 函数体内用preventDefault可以不让异常信息输出到控制台
    window.addEventListener('error', function (e) {
        console.log(e)
        const err = e.target.src || e.target.href  // 当前资源地址
        if (err) {
            console.log('捕获到资源加载异常', err)
        }
    }, true)
   <img src="./404.png" alt="">

参数为一个event对象
img

通过addEventListener(‘error’)监控静态资源加载,如果网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行
无法判断错误状态码,所以还需要配合服务端日志才能进行排查分析

捕获接口异常

框架级别的全局监听,例如aixos中使用interceptor进行拦截、Promise封装的请求catch抛出等…

 // 不使用catch时 Promise 错误抛出
 new Promise((resolve, reject) => {
  reject('error') //  报错
})

/**
 * 监听promise未处理的reject错误, 跨域情况下监控不到
*/
window.addEventListener('unhandledrejection', event => {
    console.log('捕获到未处理的promise异常',event.reason)
    return true;
})

vue项目全局异常捕获(2.2.0+)

Vue.config.errorHandler = function (err, vm, info) {
    // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
    let msg = `错误发生在:${info}中,具体信息:${err.stack}`
    console.log(msg)
}

指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue 实例

react项目异常捕获(16.x)

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { error: false };
    }

    componentDidCatch(error, info) {
         // 错误信息error.message, 错误发生的位置info.componentStack
	    this.setState({ error, info });
        // 将异常信息上报给服务器
        logErrorToMyService(error, info); 
    }

    render() {
        if (this.state.error) {
           return (
		        <div>
		          <h1>错误是:{this.state.error.toString()}</h1>
		          <h2>错误出现的位置是:{this.state.text}</h2>
		        </div>
		      )
        }

        return this.props.children;
    }
}

然后:

<ErrorBoundary>
    <MyWidget /> //监听子组件抛出的异常
</ErrorBoundary>

当有错误发生时, 我们可以友好地展示 反馈组件;
可以捕捉到它的子元素(包括嵌套子元素)抛出的异常;
可以复用错误组件

运用场景

1.可疑区域增加 try…catch,无法处理异步代码
2.全局监控JS异常: window.onerror
3.全局监控静态资源异常: window.addEventListener
4.全局捕获没有 catch 的 promise 异常:unhandledrejection
5.特定框架可以使用框架内部监听方法

四.用户监控方式

获取用户行为以及跟踪产品在用户端的使用情况,并以监控数据为基础,指明产品优化的方向
用户行为分析包含页面点击量、用户点击流、用户访问路径、用户点击热力图、用户转化率、导流转化率、用户访问时长分析和用户访问内容分析等
上报的主要数据包含:appid、userAgent、timestamp(上报的时间戳)、currentUrl(用户当前的 url)、fromUrl(前一个页面的 url)、type(上报事件的类型)、element(触发上报事件的元素)、data(自定义数据)

手动埋点(代码埋点)

纯手动写代码,调用埋点SDK的函数,在需要埋点的业务逻辑功能位置调用接口上报埋点数据,友盟、百度统计、Google Analytics、腾讯分析等第三方数据统计服务
作用:为网站产品策划提供参考支持:流量分析、用户分析、提升转化率、改进页面布局

命令式
    $(document).ready(()=>{
    // ... 这里是你的业务逻辑代码
    sendData(params);  //这里是发送你的埋点数据,params是你封装的埋点数据
    });
    // 按钮点击时发送埋点请求
    $('button').click(()=>{
    // ... 业务逻辑
    sendData(params); //同上
    });
声明式

这里声明了自定义属性data-gtag,你可以在你的SDK中去扫描和识别这些自定义属性,并解析封装数据,在SDK中按照自定义规则去绑定事件并发送埋点数据

    <button class="gtag" data-gtag="{key:'uber_comt_share_ck', act: 'click',msg:{}}">操作</button>
    // 引入jsSDK
    <script async src='https://www.google-analytics.com/analytics.js'></script>

    window.dataLayer = window.dataLayer || [];
    function gtag() {
        dataLayer.push(arguments);
    }
    gtag('js', new Date());
    gtag('config', 'UA-122876026-1');
css埋点
    .link:active::after{
        content: url("http://www.example.com?action=yourdata");
    }
    <a class="link">点击我,会发埋点数据</a>
框架方式

使用Vue或者React等前端框架,这些框架都有自己的各种生命周期,为了减少重复性的手动埋点次数,可以在各个生命周期位置,根据你的需求封装你所需的埋点。

react的react-tag-component

核心: 1.数据仓库:自定义埋点数据格式。2.埋点事件:定义数据仓库中数据的读取,打埋点事件的注入。3.埋点组件:嵌入Tag事件,用于挂载相应数据。
示例:

import React from 'react'
import { init, TD } from 'react-tag-component'

//埋点数据
const data = const data = {
    'mount': 'componentdidmount 生命周期埋点',
    'click': 'click 事件埋点'
}

/**
 *  埋点事件
 * @param {Object} bd 埋点数据,由内部注入
 * @param {Object} tag 组件上Tag对象,由内部注入
 */
function log(bd, tag) {
    const ld = bd[tag.key]  //获取埋点数据仓库对应值
    ...    //相应处理
}

//数据、事件注入
init(data)(log)

//埋点组件
export default class App extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return <div>
            <TD onClick={() => console.log('点击')} Tag={{ key: 'click', params: 'c', type: 'click' }}>
                点击时输出
            </TD>
        </div>
    }
}

vue 利用导航守卫实现对不同页面的监控

  router.beforeEach((to, from, next) => {
    // to: Route: 即将要进入的目标 路由对象
    // from: Route: 当前导航正要离开的路由
    // next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)
  })
  beforeRouteEnter (to, from, next) {
  },
  beforeRouteUpdate (to, from, next) {
  },
  beforeRouteLeave (to, from, next) {
  }

五.性能监控方式

window.performance 提供了在加载和使用当前页面期间发生的各种事件的性能计时信息(毫秒),考虑在不同网络情况下

    window.logInfo = {};  //统计页面加载时间
    window.logInfo.openTime = performance.timing.navigationStart;
    window.logInfo.whiteScreenTime = + new Date() - window.logInfo.openTime;
    document.addEventListener('DOMContentLoaded', function (event) {
        window.logInfo.readyTime = +new Date() - window.logInfo.openTime;
    });
    // 网页加载完毕执行
    window.onload = function () {
        window.logInfo.allloadTime = +new Date() - window.logInfo.openTime;
        window.logInfo.nowTime = new Date().getTime();
        var timname = {
            whiteScreenTime: '白屏时间',
            readyTime: '用户可操作时间',
            allloadTime: '总下载时间',
            mobile: '使用设备',
            nowTime: '时间',
        };
        var logStr = '';
        for (var i in timname) {
            console.warn(timname[i] + ':' + window.logInfo[i] + 'ms');
            if (i === 'mobile') {
                logStr += '&' + i + '=' + window.logInfo[i];
            } else {
                logStr += '&' + i + '=' + window.logInfo[i];
            }
        }
        // 
        (new Image()).src = '/action?' + logStr;
    };

六.采集内容

A.元信息 :是指上报最基本的信息,比如日志时间、日志唯一id等
B.用户行为信息:包括用户身份(会员/游客)、用户页面浏览、操作行为、页面停留时长等记录
C.异常信息:包括主要有两大类:运行时异常、资源加载异常
D.环境信息:主要是当前客户端宿主信息,如浏览器信息、安卓系统信息、ios系统信息、网络环境、地理位置、屏幕分辨率等
E.性能信息:包括 首屏时间:页面开始加载到第一个元素被渲染的时间、可见最大内容元素的渲染时间,小于2.5s、总下载时间、用户可交互时间

字段类型描述
requestIdString一个界面产生一个requestId 唯一性
traceIdString一个阶段产生一个traceId,用于追踪和一个异常相关的所有日志记录
hashString这条log的唯一标识码,相当于logId,但它是根据当前日志记录的具体内容而生成的
timeNumber当前日志产生的时间(保存时刻)
userIdString用户唯一标识
userStatusNumber当时,用户状态信息(是否可用/禁用)
userRolesArray当时,前用户的角色列表
userGroupsArray当时,用户当前所在组,组别权限可能影响结果
userLicensesArray当时,许可证,可能过期
pathString所在路径,URL
actionString进行了什么操作
refererString上一个路径,来源URL
prevActionObject当前界面的state、data
dataString上一个路径,来源URL
dataSourcesArray上游api给了什么数据
dataSendObject提交了什么数据
targetElementHTMLElement用户操作的DOM元素
targetDOMPathArray该DOM元素的节点路径
targetCSSObject该元素的自定义样式表
targetAttrsObject该元素当前的属性及值
errorTypeString错误类型
errorLevelString异常级别
errorStackString错误stack信息
errorFilenameString出错文件
errorLineNoNumber出错行
errorColNoNumber出错列位置
errorMessageString错误描述(开发者定义)
errorTimeStampNumber时间戳
eventTypeString事件类型
pageXNumber事件x轴坐标
pageYNumber事件y轴坐标
screenXNumber事件x轴坐标
screenYNumber事件y轴坐标
pageWNumber页面宽度
pageHNumber页面高度
screenWNumber屏幕宽度
screenHNumber屏幕高度
eventKeyString触发事件的键
networkString网络环境描述
userAgentString客户端描述
deviceString设备描述
systemString操作系统描述
appVersionString应用版本
apiVersionString接口版本
networkStatusString网络状态
whiteScreenTimeString白屏时间
readyTimeString用户可操作时间
allloadTimeString总下载时间

七.前端存储方式

可用的持久化方案可选项也比较多了,主要有:Cookie、localStorage、sessionStorage、IndexedDB、webSQL 、FileSystem 等等

在这里插入图片描述
IndexedDB 是最好的选择,它具有容量大、异步的优势,异步的特性保证它不会对界面的渲染产生阻塞

基本概念

数据库:IDBDatabase 对象
对象仓库:IDBObjectStore 对象
索引: IDBIndex 对象
事务: IDBTransaction 对象
操作请求:IDBRequest 对象
指针: IDBCursor 对象
主键集合:IDBKeyRange 对象

操作流程

//使用 IndexedDB 的第一步是打开数据库,使用indexedDB.open()方法
var request = window.indexedDB.open(databaseName, version);
var db;
// 三种事件error、success、upgradeneeded,处理打开数据库的操作结果
request.onerror = function (event) {
  console.log('数据库打开报错');
};
request.onsuccess = function (event) {
  db = request.result;
  console.log('数据库打开成功');
};
request.onupgradeneeded = function (event) {
  db = event.target.result;
}
新建数据库
新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建
request.onupgradeneeded = function(event) {
  db = event.target.result;
  var objectStore = db.createObjectStore('person', { keyPath: 'id' });
}
// 新增数据记录
function add() {
  // 写入数据需要新建一个事务。必须指定表格名称和操作模式("只读"或"读写")
  var addRequest = db.transaction(['person'], 'readwrite')
  //拿到 IDBObjectStore 对象
    .objectStore('person')
    .add({ id: 1, name: '张三', age: 24, email: 'zhangsan@example.com' });
}
// 读取数据记录
function read() {
   var readRequest = db.transaction(['person']);
   .objectStore('person');
   //参数是主键的值
   .get(1);
}
// 更新数据记录
function update() {
  var updateRequest = db.transaction(['person'], 'readwrite')
    .objectStore('person')
    .put({ id: 1, name: '李四', age: 35, email: 'lisi@example.com' });
}

八.上报数据

即时上报

收集到日志后,立即触发上报函数。仅用于A类异常。而且由于受到网络不确定因素影响,A类日志上报需要有一个确认机制,只有确认服务端已经成功接收到该上报信息之后,才算完成。否则需要有一个循环机制,确保上报成功(紧急重要)

批量上报

将收集到的日志存储在本地,当收集到一定数量之后再打包一次性上报,或者按照一定的频率(时间间隔)打包上传。这相当于把多次合并为一次上报,以降低对服务器的压力(不紧急)

区块上报

将一次异常的场景打包为一个区块后进行上报。它和批量上报不同,批量上报保证了日志的完整性,全面性,但会有无用信息。而区块上报则是针对异常本身的,确保单个异常相关的日志被全部上报 (不紧急但重要)

用户主动提交

在界面上提供一个按钮,用户主动反馈bug。这有利于加强与用户的互动(不紧急但重要)

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值