前端监控之异常监控(一)

前言

当我们的项目中假设出现了下面几种场景:

  1. 点击按钮后,页面无响应
  2. 页面跳转后显示白屏
  3. 页面卡顿
  4. ......

这些情况都是非常影响用户体验的,对于用户来说,是难以接受的,用户可能就此流失掉了。

因此前端非常有必要针对异常做一下处理!!

什么是异常

异常(Exception)。在程序中,因为语法的疏忽或其他原因而导致程序中途崩溃的情况,称之为异常。

二、异常的类型

从根本上来说,异常就是一个数据类型它有一个Error而其他异常则是它的子类

JS运行时可能会发生的错误有很多类型种错误都有对应的错误类型,而当错误发生的时候就会抛出响应的错误对象。

以下是ECMA-262 白皮书 13 版中描述了 8 种异常类型:

  1. Error:异常基类,其他错误都继承自该类型
  2. SyntaxError:语法异常
  3. ReferenceError:引用异常,尝试引用一个未被定义的变量时,将会抛出此异常
  4. RangeError:范围异常(数组越界)
  5. InternalError:内部异常
  6. TypeError: 类型异常,用来表示值的类型非预期类型时发生的错误
  7. EvalError: Eval方法异常
  8. URIError: URI 相关方法产生的异常

、异常捕获

1、try-catch

ECMA-262 第 3 版中引入了 try-catch 语句,作为 JavaScript 中处理异常的一种标准方式,基本的语法如下所示。这和 Java 中的 try-catch 语句是全完相同的。

try {// 可能会导致错误的代码
} catch (error) {// 在错误发生时怎么处理
}

如果 try 块中的任何代码发生了错误,就会立即退出代码执行过程,然后执行 catch 块。此时 catch 块会接收到一个包含错误信息的对象,这个对象中包含的信息因浏览器而异,但共同的是有一个保存着错误信息的 message 属性。

finally 子句在 try-catch 语句中是可选的,但是 finally 子句一经使用,其代码无论如何都会执行。换句话说,try 语句块中代码全部正常执行,finally 子句会执行;如果因为出错执行了 catch 语句,finally 子句照样会执行。只要代码中包含 finally 子句,则无论 try 或 catch 语句中包含什么代码——甚至是 return 语句,都不会阻止 finally 子句执行。来看下面函数的执行结果:

function testFinally {try {return "出去玩";} catch (error) {return "看电视";} finally {return "做作业";}return "睡觉";
}

表面上调用这个函数会返回 "出去玩",因为返回 "出去玩" 的语句位于 try 语句块中,而执行此语句又不会出错。实际上返回 "做作业",因为最后还有 finally 子句,结果就会导致 try 块里的 return 语句被忽略,也就是说调用的结果只能返回 "做作业"。如果把 finally 语句拿掉,这个函数将返回 "出去玩"。因此,在使用 finally 子句之前,一定要非常清楚你想让代码怎么样。(思考一下如果 catch 块和 finally 块都抛出异常,catch 块的异常是否能抛出)

但令人遗憾的是,try-catch 无法处理异步代码和一些其他场景。接下来让我具体分析几种异常场景及其处理方案。

2、window.onerror

当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行window.onerror()。

/*** @param {String}  message    错误信息* @param {String}  source     出错文件* @param {Number}  lineno     行号* @param {Number}  colno      列号* @param {Object}  error      Error对象(对象)*/
window.onerror = function (message, source, lineno, colno, error) {console.log('捕获到异常:', { message, source, lineno, colno, error })
}

同步错误可以捕获到,但是,请注意 window.error 无法捕获静态资源异常和 JS 代码错误。

3、静态资源加载异常

方法一:onerror 来捕获

<script>
function errorHandler(error) {console.log('捕获到静态资源加载异常', error)
}
</script>
<script src="http://cdn.xxx.com/js/test.js" onerror="errorHandler(this)"></script>
<link rel="stylesheet" href="http://cdn.xxx.com/styles/test.css" onerror="errorHandler(this)" />

这样可以拿到静态资源的错误,但缺点很明显,代码的侵入性太强了,每一个静态资源标签都要加上 onerror 方法。

方法二:addEventListener("error")

<!DOCTYPE html>
<html lang="zh"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>error</title><script>window.addEventListener('error', (error) => {console.log('捕获到异常:', error);}, true)</script>
</head><body><img src="https://itemcdn.zcycdn.com/15af41ec-e6cb-4478-8fad-1a47402f0f25.png">
</body></html>

由于网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行,但是这种方式虽然可以捕捉到网络请求的异常,但是无法判断 HTTP 的状态是 404 还是其他比如 500 等等,所以还需要配合服务端日志才进行排查分析才可以。

4、Promise 异常

Promise 中的异常不能被 try-catch 和 window.onerror 捕获,这时候我们就需要监听 unhandledrejection 来帮我们捕获这部分错误。

window.addEventListener('unhandledrejection', function (e) {e.preventDefault()console.log('捕获到 promise 错误了')console.log('错误的原因是', e.reason)console.log('Promise 对象是', e.promise)return true
})Promise.reject('promise error')
new Promise((resolve, reject) => {reject('promise error')
})
new Promise((resolve) => {resolve()
}).then(() => {throw 'promise error'
})

5、请求异常

以最常用的 HTTP 请求库 axios 为例,模拟接口响应 401 的情况:

// 请求
axios.get('/api/test/401')
Uncaught (in promise) Error: Request failed with status code 401
at createError (axios.js:1207)
at settle (axios.js:1177)
at XMLHttpRequest.handleLoad (axios.js:1037)

可以看出来 axios 的异常可以当做 Promise 异常来处理:

// 请求
axios.get('http://localhost:3000/api/uitest/sentry/401')
.then((data) => console.log('接口请求成功', data))
.catch((e) => console.log('接口请求出错', e))
# 结果
接口请求出错 Error: Request failed with status code 401
at createError (createError.js:17)
at settle (settle.js:18)
at XMLHttpRequest.handleLoad (xhr.js:62)

一般接口 401 就代表用户未登录,就需要跳转到登录页,让用户进行重新登录,但如果每个请求方法都需要写一遍跳转登录页的逻辑就很麻烦了,这时候就会考虑使用 axios 的拦截器来做统一梳理,同理能统一处理的异常也可以在放在拦截器里处理。

// Add a response interceptor
axios.interceptors.response.use(function (response) {// Any status codes that falls outside the range of 2xx cause this function to trigger// Do something with response error},function (error) {if (error.response.status === 401) {goLogin() // 跳转登录页} else if (error.response.status === 502) {alert(error.response.data.message || '系统升级中,请稍后重试')}return Promise.reject(error.response)}
)

6、React 异常

React 处理异常的方式不同。虽然 try-catch 适用于许多非普通 JavaScript 应用程序,但它只适用于命令式代码。因为 React 组件是声明性的,所以 try-catch 不是一个可靠的选项。为了弥补这一点,React 实现了所谓的错误边界。错误边界是 React 组件,它“捕获子组件树中的任何地方的 JavaScript 错误”,同时还记录错误并显示回退用户界面。

class ErrorBoundary extends React.Component {constructor(props) {super(props)this.state = { hasError: false }}componentDidCatch(error, info) {// 展示出错的UIthis.setState({ hasError: true }) // 将错误信息上报到日志服务器logErrorToMyService(error, info)}render() {if (this.state.hasError) {// 可以展示自定义的错误样式return <h1>Something went wrong.</h1>}return this.props.children}
}

但是需要注意的是, error boundaries 并不会捕捉下面这些错误:

  1. 事件处理器
  2. 异步代码
  3. 服务端的渲染代码
  4. 在 error boundaries 区域内的错误

我们可以这样使用 ErrorBoundary:

<ErrorBoundary><MyWidget />
</ErrorBoundary>

7、Vue 异常

全局异常捕获

Vue.config.errorHandler

Vue.config.errorHandler = (err, vm, info) => {console.error('通过vue errorHandler捕获的错误')console.error(err)console.error(vm)console.error(info)
}

组件内异常捕获

组件内使用errorCaptured

8、总结

异常处理时需分清是致命错误还是非致命错误。

  1. 可疑区域增加 try-catch
  2. 全局监控 JS 异常 window.onerror
  3. 全局监控静态资源异常 window.addEventListener
  4. 捕获没有 catch 的 Promise 异常用 unhandledrejection
  5. Vue errorHandler 和 React componentDidCatch
  6. Axios 请求统一异常处理用拦截器 interceptors
  7. 使用日志监控服务收集用户错误信息

异常上报

异常捕获之后,接下来就是数据上报的工作了。

目前主要由以下3种上报信息方式:

方案

优点

缺点

img请求

兼容性

部分浏览器丢点,延迟页面卸载;

get请求长度限制

Fetch/XHR

兼容性

Fetch丢点同步

XHR不丢点,但延迟页面卸载

Navigator.sendBeacon()

不丢点不会延迟页面卸载

兼容性

丢点:在浏览器点击跳转时,跳转前的点击上报请求都会进行一个三次握手,如果此时,网络较慢、服务器运行缓慢或者上报请求还在处理阶段,这时,如果页面被卸载了,浏览器都会自动对当前的请求进行abort。这样,这个http的请求就没有建立,导致上报没有真正发出。

下面的例子展示了一个理论上的统计代码——在卸载事件处理器中尝试通过一个同步的 XMLHttpRequest 向服务器发送数据。这导致了页面卸载被延迟。

window.addEventListener('unload', logData, false)
function logData() {var client = new XMLHttpRequest()client.open('POST', '/log', false) // 第三个参数表明是同步的 xhrclient.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8')client.send(analyticsData)
}

这就是 sendBeacon() 方法存在的意义。使用 sendBeacon() 方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。这就解决了提交分析数据时的所有的问题:数据可靠,传输异步并且不会影响下一页面的加载。此外,代码实际上还要比其他技术简单许多!

下面的例子展示了一个理论上的统计代码模式——通过使用 sendBeacon() 方法向服务器发送数据。

window.addEventListener('unload', logData, false);function logData() {navigator.sendBeacon("/log", analyticsData);
}

所以这里的推荐是优先检查浏览器是否支持Navigator.sendBeacon(),不支持的话再使用其他上报方式。

参考资料

ecma-262: https://www.ecma-international.org/publications-and-standards/standards/ecma-262/

ES6th 白皮书: https://262.ecma-international.org/6.0

https://juejin.cn/post/6932620551827488775

https://juejin.cn/post/6936562262480158728

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/53507.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker 安装 Wordpress 用lnmp搭建出现的故障

第一个故障就是mysql出现的故障了 你起mysql镜像是这么起的导致pid号用不了 docker run --namemysql -d --privileged --device-write-bps /dev/sda:10M -v /usr/local/mysql --net mynetwork --ip 172.20.0.20 mysql:lnmp 解决方法 docker run --namemysql -d --privilege…

vue Promise 对象 等待所有异步处理完成 再继续处理

1 定义数据集合 用来搜集所有数据 let promises []; // 用来存储所有的 Promise 对象 2 promise对象 异步 返回数据 同时添加数据到promises 列表 // 依次读取列表元素的表 for (let symbol of symbolList) {let promise new Promise((resolve, reject) > { // 将请求…

解决前后端交互Long类型精度丢失的问题

1、全局注解 package com.jiawa.train.common.config;import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import org.springframework.c…

【MySQL系列】统计函数(count,sum,avg)详解

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Java中HashMap的基本介绍和详细讲解,HashMap的遍历以及HashMap的底层源码的分析

HashMap 基本介绍 HashMap 是 Java 中的一个集合类&#xff0c;实现了 Map 接口&#xff0c;用于存储键值对&#xff08;key-value&#xff09;数据。它基于哈希表的数据结构实现&#xff0c;可以实现高效的查找、插入和删除操作。 HashMap 细节讨论 无序性&#xff1a; Has…

[Linux]命令行参数和进程优先级

[Linux]命令行参数和进程优先级 文章目录 [Linux]命令行参数和进程优先级命令行参数命令行参数的概念命令函参数的接收编写代码验证 进程优先级进程优先级的概念PRI and NI使用top指令修改nice值 命令行参数 命令行参数的概念 命令行参数是指用于运行程序时在命令行输入的参数…

Qt6和Rust结合构建桌面应用

桌面应用程序是原生的、快速的、安全的&#xff0c;并提供Web应用程序无法比拟的体验。 Rust 是一种低级静态类型多范式编程语言&#xff0c;专注于安全性和性能&#xff0c;解决了 C/C 长期以来一直在努力解决的问题&#xff0c;例如内存错误和构建并发程序。 在桌面应用程序开…

css盒模型

盒模型的组成&#xff1a; content&#xff0c;padding&#xff0c;border&#xff0c;margin 盒模型的分类&#xff1a; 内容盒模型(标准盒模型) — 盒子的宽widthpaddingborder 边框盒模型 — 盒子的宽width 参考 盒模型【CSS面试题】_哔哩哔哩_bilibili

ToolAI–全球最完整最全面的AI人工智能工具集合

ToolAI是一个全球最完整最全面的AI人工智能工具集合网站&#xff0c;收集了全球最完整的数千个AI网站、工具、app&#xff0c;包含文案写作、邮件助手、聊天机器人、社交媒体等等各种行业类型的AI工具&#xff0c;可以按照地区或者分类进行查找浏览&#xff0c;目前收集6800 人…

垃圾回收器

垃圾回收器就是垃圾回收的实践者&#xff0c;随着JDK的发展&#xff0c;垃圾回收器也在不断的更迭&#xff0c;在不同的场合下使用不同的垃圾回收器&#xff0c;这也是JVM调优的一部分。 1.垃圾回收器的分类 按线程可分为单线程(串行)垃圾回收器和多线程(并行)垃圾回收器。 按…

【HTML】学习笔记(自用持续更新)

HTML基本框架 只定义信息&#xff0c;通过标签组成整个页面框架&#xff0c;再通过CSS渲染使得前端页面做的好看 <!DOCTYPE HTML> \\html类型 <html><body><h1>这是一个标题</h1><p>这是一段文字</p></body> </html> …

Java之ApI之Math类详解

1 Math类 1.1 概述 tips&#xff1a;了解内容 查看API文档&#xff0c;我们可以看到API文档中关于Math类的定义如下&#xff1a; Math类所在包为java.lang包&#xff0c;因此在使用的时候不需要进行导包。并且Math类被final修饰了&#xff0c;因此该类是不能被继承的。 Math类…

“图为科技——什么是边缘计算“

边缘计算是一种新兴的计算模式&#xff0c;它将计算资源和服务推向离终端设备更近的地方。以往&#xff0c;计算任务大多集中在云端进行&#xff0c;但随着物联网和移动互联网的快速发展&#xff0c;边缘计算应运而生。 边缘计算的核心思想是将计算、存储和网络功能部署在离终端…

Unity 之 GameObject.Find()在场景中查找指定名称的游戏对象

文章目录 GameObject.Find 是 Unity 中的一个函数&#xff0c;用于在场景中查找指定名称的游戏对象。这个函数的主要作用是根据游戏对象的名称来查找并返回一个引用&#xff0c;使您能够在代码中操作该对象。以下是有关 GameObject.Find 的详细介绍&#xff1a; 函数签名&…

某网站DES加密逆向分析实战

文章目录 一、抓包分析二、加密分析一、重写加密 一、抓包分析 分析站点&#xff1a; aHR0cDovL2VpcC5jaGFuZmluZS5jb20v 首先我们提交一下登陆信息&#xff1a; 搜索j_password查看加密函数: 把上图搜索到的encryptPassword函数拿出来分析一下&#xff1a; function encryptP…

智慧能源管理系统助力某制造企业提高能源利用效率

随着全球能源需求不断增加和能源价格的上涨&#xff0c;企业和机构日益意识到能源管理的重要性。传统的能源管理方式不仅效率低下&#xff0c;还容易造成资源浪费和环境污染。因此&#xff0c;许多企业开始探索采用智慧能源管理系统来提高能源利用效率&#xff0c;降低能源成本…

【面向大一新生IT技术社群招新啦,不来瞅瞅?】

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生 &#x1f43b;‍❄️个人主页&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;落798. &#x1f54a;️系列专栏&#xff1a;【零基础学java】 ----- 【重识c语言】 ---- 【计算机网络】—【Spri…

python爬虫-网页数据提取

import requests #headers 网页右键->Network->最下面的User-Agent复制。 headers {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"} #你想要的网址 url &q…

5分钟理解NPL算法 之 马尔可夫链 Markov Chain

马尔可夫链&#xff08;Markov Chain&#xff09; 马尔可夫链是一种简单的推理模型。用于描述受当前事件影响下的下一事件发生概率。在预测学科中广泛应用。例如股票预测、文字推理、路线推荐等。 他的核心思路是&#xff1a;假设事件顺序为: X 1 , X 2 , X 3 , . . . . . X…

【学习笔记】[ABC274Ex] XOR Sum of Arrays

有点难&#x1f605; 真的是 A B C ABC ABC的难度吗&#x1f605; 非常精妙的哈希题目。 定义矩阵乘法&#xff1a; c i , j ⊕ ( a i , k & b k , j ) c_{i,j}\oplus (a_{i,k}\& b_{k,j}) ci,j​⊕(ai,k​&bk,j​) 之所以可以矩阵乘法是因为满足 ( a ⊕ b )…