0
点赞
收藏
分享

微信扫一扫

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK

前言

这是学习源码整体架构第四篇。整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现。文章学习的是打包整合后的代码,不是实际仓库中的拆分的代码。

其余三篇分别是:

1.学习 jQuery 源码整体架构,打造属于自己的 js 类库

2.学习underscore源码整体架构,打造属于自己的函数式编程类库

3.学习 lodash 源码整体架构,打造属于自己的函数式编程类库

导读
本文通过梳理前端错误监控知识、介绍 ​​sentry​​错误监控原理、 ​sentry​​初始化、 ​Ajax​​上报、 ​window.onerrorwindow.onunhandledrejection​​几个方面来学习 ​sentry​的源码。

开发微信小程序,想着搭建小程序错误监控方案。最近用了丁香园 开源的 ​Sentry​​ 小程序 ​SDK​​sentry-miniapp。 顺便研究下 ​​sentry-javascript​仓库 的源码整体架构,于是有了这篇文章。

本文分析的是打包后未压缩的源码,源码总行数五千余行,链接地址是:https://browser.sentry-cdn.com/5.7.1/bundle.js, 版本是 ​v5.7.1​。

本文示例等源代码在这我的 ​github​​博客中github blog sentry,需要的读者可以点击查看,如果觉得不错,可以顺便 ​star​一下。

看源码前先来梳理下前端错误监控的知识。

前端错误监控知识

摘抄自 慕课网视频教程:前端跳槽面试必备技巧
别人做的笔记:前端跳槽面试必备技巧-4-4 错误监控类

前端错误的分类

1.即时运行错误:代码错误

try...catch

window.onerror​​ (也可以用 ​DOM2​事件监听)

2.资源加载错误

object.onerror​​: ​dom​​对象的 ​onerror​事件

performance.getEntries()

Error​事件捕获

3.使用 ​performance.getEntries()​获取网页图片加载错误

varallImgs=document.getElementsByTagName('image')

varloadedImgs=performance.getEntries().filter(i=>i.initiatorType==='img')

最后 ​allIms​​和 ​loadedImgs​对比即可找出图片资源未加载项目

Error事件捕获代码示例

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数


上报错误的基本原理

1.采用 ​Ajax​通信的方式上报

2.利用 ​Image​对象上报 (主流方式)

Image​​上报错误方式: ​(newImage()).src='https://lxchuan12.cn/error?name=若川'

Sentry 前端异常监控基本原理

1.重写 ​window.onerror​​ 方法、重写 ​window.onunhandledrejection​ 方法

如果不了解 ​onerroronunhandledrejection​​方法的读者,可以看相关的 ​MDN​文档。这里简要介绍一下:

MDN GlobalEventHandlers.onerror

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_02

参数:
​​message​​:错误信息(字符串)。可用于 ​HTML onerror=""​​处理程序中的 ​event​​。
​​source​​:发生错误的脚本 ​URL​​(字符串)
​​lineno​​:发生错误的行号(数字)
​​colno​​:发生错误的列号(数字)
​​error​​: ​Error​对象(对象)

MDN unhandledrejection

当 ​Promise​​ 被 ​reject​​ 且没有 ​reject​​ 处理器的时候,会触发 ​unhandledrejection​​ 事件;这可能发生在 ​window​​ 下,但也可能发生在 ​Worker​ 中。 这对于调试回退错误处理非常有用。

Sentry​​ 源码可以搜索 ​global.onerror​ 定位到具体位置

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_03

同样,可以搜索 ​global.onunhandledrejection​ 定位到具体位置

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_04

2.采用 ​Ajax​上传

支持 ​fetch​​ 使用 ​fetch​​,否则使用 ​XHR​。

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_05

2.1 ​fetch

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_06

2.2 ​XMLHttpRequest

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_07

接下来主要通过Sentry初始化、如何 ​Ajax上报​​和 ​window.onerrorwindow.onunhandledrejection​三条主线来学习源码。

如果看到这里,暂时不想关注后面的源码细节,直接看后文小结1和2的两张图。或者可以点赞或收藏这篇文章,后续想看了再看。

Sentry 源码入口和出口

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_08

Sentry.init 初始化 之 init 函数

初始化

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_09


getGlobalObject、inNodeEnv 函数

很多地方用到这个函数 ​getGlobalObject​​。其实做的事情也比较简单,就是获取全局对象。浏览器中是 ​window​。

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_10

继续看 ​initAndBind​ 函数

initAndBind 函数之 new BrowserClient(options)

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_11

可以看出 ​initAndBind()​​,第一个参数是 ​BrowserClient​​ 构造函数,第二个参数是初始化后的 ​options​​。 接着先看 构造函数 ​​BrowserClient​​。 另一条线 ​​getCurrentHub().bindClient()​ 先不看。

BrowserClient 构造函数

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_12

从代码中可以看出​​: ​BrowserClient​​ 继承自 ​BaseClient​​,并且把 ​BrowserBackend​​, ​options​​传参给 ​BaseClient​调用。

先看 ​BrowserBackend​​,这里的 ​BaseClient​,暂时不看。

看 ​BrowserBackend​之前,先提一下继承、继承静态属性和方法。

__extends、extendStatics 打包代码实现的继承

未打包的源码是使用 ​ES6extends​​实现的。这是打包后的对 ​ES6​​的 ​extends​的一种实现。

1. // 继承静态方法和属性​​
2. ​​var extendStatics = function(d, b) {​​
3. ​​ // 如果支持 Object.setPrototypeOf 这个函数,直接使用​​
4. ​​ // 不支持,则使用原型__proto__ 属性,​​
5. ​​ // 如何还不支持(但有可能__proto__也不支持,毕竟是浏览器特有的方法。)​​
6. ​​ // 则使用for in 遍历原型链上的属性,从而达到继承的目的。​​
7. ​​ extendStatics = Object.setPrototypeOf ||​​
8. ​​ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||​​
9. ​​ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };​​
10. ​​ return extendStatics(d, b);​​
11. ​​};​​
12.
13. ​​function __extends(d, b) {​​
14. ​​ extendStatics(d, b);​​
15. ​​ // 申明构造函数__ 并且把 d 赋值给 constructor​​
16. ​​ function __() { this.constructor = d; }​​
17. ​​ // (__.prototype = b.prototype, new __()) 这种逗号形式的代码,最终返回是后者,也就是 new __()​​
18. ​​ // 比如 (typeof null, 1) 返回的是1​​
19. ​​ // 如果 b === null 用Object.create(b) 创建 ,也就是一个不含原型链等信息的空对象 {}​​
20. ​​ // 否则使用 new __() 返回​​
21. ​​ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());​​
22. ​​}​​

不得不说这打包后的代码十分严谨,上面说的我的文章 面试官问:JS的继承​ 中没有提到不支持 ​__proto__​​的情况。看来这文章可以进一步严谨修正了。 让我想起 ​​Vue​​源码中对数组检测代理判断是否支持 ​__proto__​的判断。

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_13

看完打包代码实现的继承,继续看 ​BrowserBackend​ 构造函数

BrowserBackend 构造函数 (浏览器后端)

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_14

BrowserBackend​​ 又继承自 ​BaseBackend​。

BaseBackend 构造函数 (基础后端)

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_15

通过一系列的继承后,回过头来看 ​BaseClient​ 构造函数。

BaseClient 构造函数(基础客户端)

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_16

小结1. new BrowerClient 经过一系列的继承和初始化

可以输出下具体 ​newclientClass(options)​之后的结果:

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_17

最终输出得到这样的数据。我画了一张图表示。重点关注的原型链用颜色标注了,其他部分收缩了。

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_18

initAndBind 函数之 getCurrentHub().bindClient()

继续看 ​initAndBind​ 的另一条线。

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_19

获取当前的控制中心 ​Hub​​,再把 ​newBrowserClient()​​ 的实例对象绑定在 ​Hub​上。

getCurrentHub 函数

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_20

衍生的函数 getMainCarrier、getHubFromCarrier

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_21

bindClient 绑定客户端在当前控制中心上

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_22

小结2. 经过一系列的继承和初始化

再回过头来看 ​initAndBind​函数

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_23

最终会得到这样的 ​Hub​实例对象。笔者画了一张图表示,便于查看理解。

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_24

初始化完成后,再来看具体例子。 具体 ​captureMessage​ 函数的实现。

Sentry.captureMessage('Hello, 若川!');

captureMessage 函数

通过之前的阅读代码,知道会最终会调用 ​Fetch​接口,所以直接断点调试即可,得出如下调用栈。 接下来描述调用栈的主要流程。

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_25

调用栈主要流程:

captureMessage

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_26

=> callOnHub

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_27

=> Hub.prototype.captureMessage

接着看 ​Hub.prototype​​ 上定义的 ​captureMessage​ 方法

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_28

=> Hub.prototype._invokeClient

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_29

=> BaseClient.prototype.captureMessage

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_30

最后会调用 ​_processEvent​ 也就是

=> BaseClient.prototype._processEvent

这个函数最终会调用

_this._getBackend().sendEvent(finalEvent);

也就是

=> BaseBackend.prototype.sendEvent

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_构造函数_31

=> FetchTransport.prototype.sendEvent 最终发送了请求

FetchTransport.prototype.sendEvent

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_赋值_32

看完 ​Ajax上报​​ 主线,再看本文的另外一条主线 ​window.onerror​ 捕获。

window.onerror 和 window.onunhandledrejection 捕获 错误

例子:调用一个未申明的变量。

func();

Promise​ 不捕获错误

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_33


captureEvent

调用栈主要流程:

window.onerror

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_34

window.onunhandledrejection

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_35

共同点:都会调用 ​currentHub.captureEvent

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK_初始化_36

=> Hub.prototype.captureEvent

最终又是调用 ​_invokeClient​​ ,调用流程跟 ​captureMessage​ 类似,这里就不再赘述。

this._invokeClient('captureEvent')

=> Hub.prototype._invokeClient

=> BaseClient.prototype.captureEvent

=> BaseClient.prototype._processEvent

=> BaseBackend.prototype.sendEvent

=> FetchTransport.prototype.sendEvent

最终同样是调用了这个函数发送了请求。

可谓是殊途同归,行文至此就基本已经结束,最后总结一下。

总结

Sentry-JavaScript​​源码高效利用了 ​JS​的原型链机制。可谓是惊艳,值得学习。

本文通过梳理前端错误监控知识、介绍 ​sentry​​错误监控原理、 ​sentry​​初始化、 ​Ajax​​上报、 ​window.onerrorwindow.onunhandledrejection​​几个方面来学习 ​sentry​的源码。还有很多细节和构造函数没有分析。

总共的构造函数(类)有25个,提到的主要有9个,分别是: ​HubBaseClientBaseBackendBaseTransportFetchTransportXHRTransportBrowserBackendBrowserClientGlobalHandlers​。

其他没有提到的分别是 ​SentryErrorLoggerMemoSyncPromisePromiseBufferSpanScopeDsnAPINoopTransportFunctionToStringInboundFiltersTryCatchBreadcrumbsLinkedErrorsUserAgent​。

这些构造函数(类)中还有很多值得学习,比如同步的 ​Promise​​(SyncPromise)。 有兴趣的读者,可以看这一块官方仓库中采用 ​​typescript​写的源码SyncPromise,也可以看打包后出来未压缩的代码。

举报

相关推荐

0 条评论