GithubHelp home page GithubHelp logo

saijs / wiki Goto Github PK

View Code? Open in Web Editor NEW
380.0 380.0 101.0 1.59 MB

JavaScript 异常档案,沉淀、交流与分享。

Home Page: https://saijs.github.io/wiki/

Makefile 0.04% JavaScript 93.04% CSS 5.15% HTML 1.76%

wiki's People

Contributors

dqw avatar hotoo avatar lin04com avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wiki's Issues

每周异常:第 1 期 (20130715)

2013-07-15 14:00~14:03,来自北京市时代互联(180.184.99.37) 的 pc/-1|windows/6.2|firefox/22.0|gecko/20100101 用户向支付宝 katongweb 系统发起 1480次猛烈的总攻,详情如下:

KEY VALUE
Ref https://zht.alipay.com/asset/bankList.htm
URL https://katongweb.alipay.com/moto/motoEntry.htm
File chrome://grabword/content/grabword.js
Line 0
Message Script error.
stacktrace

该用户通过 Ref 页面到目标 URL 之后,连续不断的通过 Firefox 插件脚本触发脚本异常。


通过最近的几次异常报警来看,由客户端连续发起攻击引起的 JavaScript 异常 PV
非正常波动,对异常监控数据有比较大的影响。

因此考虑以下方案来提升监控数据的稳定有效性:

  • 监控 JavaScript 异常 UV(根据 Session)
  • 监控 JavaScript 异常独立 IP 数。
  • 排除客户端本地脚本引起的异常(多数攻击是由客户端脚本发起)
    • 今日客户端脚本异常率:45.14%
    • 正常情况下客户端脚本异常率:3.45%

下图是排除了客户端本地脚本抛出的异常之后的结果:

2013-07-16 3 14 24

(当天数据对比,图中圆点是开启过滤客户端本地异常的时间点,下同)

2013-07-16 3 16 22

(相近2天数据对比)

每周异常:第 9期,搜狗浏览器中神一样的 try/catch 特性

背景

某日 16时 30分,监控实时大盘中全站 JavaScript 异常和 404 异常分别朝着不同的方向延伸,橙色的 JavaScript 异常急剧上升。

bac3c01389cadbb12d4873520b3bd2a9

详细数据中我们看到一向高标准高质量的收银台出现异常大量的异常。

2013-09-13 4 00 58

排查

我们发现其中有两个页面异常最多,而这两个页面中异常最多的『一个异常』详情如下:

  • File: 同页面 URL
  • Line: 1
  • Message: Uncaught SyntaxError: Unexpected token a

客户端信息:

  • pc/-1;windows/5.1;sg/2.x;webkit/535.1
  • pc/-1;windows/6.1;sg/2.x;webkit/535.1

堆栈信息:

at function parse()
    at function ()
    at function (data)
    at function ()
    at function ()

分析过程非常艰辛,重现异常的过程也是一波三折,这里不做赘述,最终分析得出:

  • 搜狗浏览器的 userAgent 太坑爹,几乎所有版本都是 2.x,开发者太不专业了。最终找到内核为 webkit/535.1 的是 3.2 版。

  • 重现到老版 arale 中的 ajax 模块中对 JSON 有特殊的处理,如果浏览器内置的 JSON 不支持非标准的 JSON (如 {a:1})则 hack 做兼容。

        function W3CParse(data) {
            if (validJSON(data)) {
                return window.JSON.parse(data)
            } else {
                return null;
                arale.error("Invalid JSON: " + data)
            }
        }
    
        function defaultParse(data) {
            if (validJSON(data)) {
                return (new Function("return (" + data + ")"))()
            } else {
                return null;
                arale.error("Invalid JSON: " + data)
            }
        }
        var ok_wrong_json = function () {
            try {
                JSON.parse("{ a: 1 }");
                return true
            } catch (x) {
                return false
            }
        };
        if (window.JSON && window.JSON.parse && ok_wrong_json()) {
            parseJSON = function (data) {
                return W3CParse.call(this, data)
            }
        } else {
            parseJSON = function (data) {
                return defaultParse.call(this, data)
            }
        }
  • 抛出异常的代码是 JSON.parse("{a: 1}")

  • 但奇怪的是这段代码是放在 try/catch 中,为什么还会有异常被监控捕获?

  • 最终发现这是搜狗浏览器 3.2版极速(webkit)模式中的特性:即使 try/catch 住的异常,同样会被 window.onerror 捕获,但并未因此中断业务逻辑,后续的代码仍然会按照正确的 try/catch 异常处理流程进行,所以对业务本身没有影响。

  • p.s. 搜狗浏览器没有控制台,用户不会知道出了异常。

  • 另外收银台之前异常量少的主要原因是主要的页面没有引入前端监控,用户抛出了异常而我们不知道而已。

相关截图

2013-09-13 4 39 14
2013-09-13 4 39 33
2013-09-13 4 40 26
2013-09-13 4 40 12
2013-09-13 4 41 03

解决方案

最初虽然有些争议,但我们最终决定的处理方案是监控中临时排除 sg/2.x|webkit/535.1 中 Uncaught SyntaxError: Unexpected token a 异常。

是否要排除这个浏览器中所有的异常?

呃,考虑到搜狗浏览器的份额,我们的策略是只排除已知的异常,未知的异常看最终分析结果再考虑。

广告

这个异常排查的主要功臣 @wsvn53,我们在排查过程中频繁使用了他开发的工具 Fedit,可以直接修改线上代码。排查线上故 障、线下接⼝什么的都非常⽅方便。强烈建议⼤家都装上⽤用。

最后

我仅代表我自己,想说某些毫无责任心的国产浏览器壳厂商们,你们没有创造价值,只是在各种不同方式的索取。

每周异常:第 7期,保持简单,保持拙朴(20130828)

副标题:如何简单有效的发现最重要的有效异常

Keep It Simple, Stupid.

-- U.S. Navy (1960)

在这之前,我一直苦苦寻求牛逼的算法 #12 ,来解决这个看似简单,其实不易;仿佛不易,最终简单的问题:找出最重要的有效异常。

最早的时候,我们定义了『一个异常』由以下核心部分组成:

  • URL: 异常所在页面。
  • File: 异常代码所在文件。
  • Line: 异常代码所在行数。
  • Message: 异常消息。

这些是不变的,相同页面中,同一个异常代码文件同一行,异常消息相同的异常,我们可以近似的认为它是同一个异常。

另外还定义了异常率:

  • 异常率(PV):『一个异常』的 PV,除以这个异常所在页面的 PV。
  • 异常率(UV):『一个异常』的 UV,除以这个异常所在页面的 UV。

于是我们通过 URL, File, Line, Message 四个核心信息来统计每一个异常的 PV, UV, PV异常率,UV异常率。并按照其中的某个字段为主进行排序。

但是得到的结果总是让人不尽如人意,总会有很多无法排查,甚至还有一些显然不是我们代码的问题的异常排前排。

我们尝试限制异常 PV, UV 的基数,尝试限制页面 PV, UV 的基数,结果仍然不理想。
而且也没有想出其他牛逼的权重算法来评估每个异常。

我们想过异常是跟代码所在文件和行号、异常消息有关系的,跟页面 URL 的关系不那么强,因为公共代码会在很多页面使用,把这同一个异常分散算到不同页面中会严重影响这个异常的排名。

但是实际上公共代码(尤其是基础类库)的异常很多情况下是业务系统调用不当,或者使用的版本有 BUG 导致,最终还是需要这个页面的开发者调整调用方法,或升级使用更新的公共脚本来解决。所以忽略或弱化异常所在页面是不可接受的。

而且排除了异常所在页面的信息,对于排查异常来说更为困难。

够了,我已经受够了,脑袋一片浆糊。
累、困,早点回家睡觉。


回到家随手画了几笔,然后去洗澡。洗澡的时候想着刚刚画的思维草图,逐渐想清楚了很多。

  • File: 由于是 seajs 自动管理依赖,以及 combo 的使用,异常所在文件本身具有不稳定性。
  • Line: 由于上面 File 的原因,不仅仅是 HTML 代码中的异常行号不稳定,静态脚本异常行号也具有不稳定性。
  • Message: 不同浏览器(包括不同版本,或者在不同操作系统中)对同一个异常有不同的消息反馈,另外还有本地化的原因,异常消息也具有不稳定性。

最后,只有异常所在页面 URL(或者页面 ID,一个页面源文件可能有多个 URL 地址,但是它们是有关系的,ID 相同)才是最稳定的。


于是我根据页面 URL 排列出每个页面的异常 PV, UV, PV异常率,UV异常率。并根据 PV 异常率为主进行倒序排序(每次访问都抛出的异常,比每个用户访问都抛出的异常的优先级更高)于是我们轻易得出了全站异常率最高的页面。

根据这个关键的页面信息,再次查询出每『一个异常』的 PV, UV, PV异常率,UV异常率。

以不变(页面 URL 或 ID),应万变。我们一个个把这些异常大户揪出来,各个击破。即使暂时没有发布解决,我们仍可以轻易识别排除这个已经揪出的异常。

好了,妈妈再也不用操心我的每周异常 TOP 数据分析了。

嗯, #12 这个 issue 也可以关了。


在做静态资源异常监控时,也有类似的经历。

静态资源监控最重要的是发现哪个页面引用了哪个 404资源,尤其是漏发的资源。因此最重要的有两个关联维度:

  • 404资源所在页面
  • 404资源文件地址

但是引起 404 的场景很多,包括:

  • 漏发文件。
  • 静态引用地址错误。
  • 动态引用地址错误。

其中动态引用是由于动态脚本计算得出的静态资源地址,引发的 404 非常频繁,多样、而且分布的很散。因此这两个维度的排列组合结果就非常巨大。而且我们的分析系统是基于索引的,这些内容不确定、数量庞大的指标非常不适合用这个系统分析。(我们还有另一个非常时候实时异常分析的系统,这里不做介绍)

因此我们把重心放在更为稳定的页面 URL,只关注出现 404 的页面,至于是什么资源可以直接根据 URL 查询日志,或直接访问这个页面。

小结

这篇讲的几个关键内容再强调下:

  • 保持简单,保持拙朴。
  • 抓住重点,真正的核心重点。

终于可以睡了,晚安~

每周异常:第 4期,如何避免客户端攻击造成的大量异常报警 (20130807)

在 JavaScript 异常监控过程中,发现有很多由于客户端连续攻击造成的大量异常,导致监控系统报警。

2013-08-07 9 58 37

(图中大量不连续的波动,都是攻击造成的)

这种少量用户(UV)发起攻击造成的大量异常(PV)波动,不是我们实时监控需要关注的部分。
我们实时监控最重要的是要发现由于系统发布过程中,我们的发布的代码有问题导致大量用户受影响而出现的波动(波形可能跟上图的每一个异常波形相似)。

理论上导致异常数据波动的场景包括:

  • 少量用户发起大量攻击。
  • 大量用户同时涌入或离开。
  • 发布问题代码影响到大量用户。

少量用户发起大量攻击造成数据波动,是我们可以不必实时关注实时报警的部分。

大量用户同时涌入或离开造成的异常数量(PV)波动,也是我们无需实时关注的部分,另外我们也可以通过异常率规避波动。

场景 异常 PV 异常 UV 异常率(PV) 异常率(UV)
攻击 波动 无波动 波动 无波动
涌入 波动 波动 无波动 无波动
发布 波动 波动 波动 波动

注:

  • 异常率(PV) = 异常PV / 所在页面PV
  • 异常率(UV) = 异常UV / 所在页面UV

从上面的对照表可以看出,理论上我们只需要关注异常率(UV) 是否正常波动就可以了。

理想很性感,现实很骨感。

我们的数据仓库、数据分析系统对于实时 PV 有很强的处理能力,但是对于 UV 就感觉力有不逮。

起初想到在实时日志处理逻辑中使用缓存(伪代码):

class LogParse(){

  var user_cache = {};
  var MINUTES_FORMAT = "YYYYMMDDHHmm";
  var lastTime = moment().format(MINUTES_FORMAT);

  function parse(log){
    // PV 统计

    var now = moment().format(MINUTES_FORMAT);
    if(now !== lastTime){
      user_cache = {};
      lastTime = now;
    }
    if(user_cache.hasOwnProperty(user_id)){return;}
    // UV 统计
  }

}
  • 但是实时数据分析系统是分布式的,由多台服务器组成的集群同时处理日志,所以里面的内部状态只有本机有效
  • 另外更致命的是,实时数据分析系统是无状态的,下次执行会 new 新的 LogParser 实例,同一台机器的状态也保证不了。

于是我们又想到一种从客户端来标识用户的方法:

  1. 客户端单位时间内同一个页面抛出的同一个异常,第一次打上 uv 标记。
  2. 数据处理时遇到 uv 标记的日志,同时统计算做一个异常 uv。

由于客户端时间和服务端有时间差,每个客户端时间和服务端时间差也不一致,导致这种方案会有稍大误差,但也基本能解决问题。

但是要在客户端打标记,则需要在客户端(cookie 或其他本地存储)记录每个异常(url, file, line, message 相同被当作同一个异常)最后抛出的时间,每次抛出异常都需要读写本地存储,稍嫌臃肿。

通过深入分析实际的攻击案例,发现攻击引发大量异常的场景中,客户端页面是不刷新或重新访问的,都是直接在当前访问的页面执行上批量脚本。

因此我们可以使用局部变量,将当前访问的页面的每个异常最后抛出时间记录在内存中,重新访问页面则重新开始记录。

这种方案对于防范攻击造成的大量异常非常有效;对于大量用户涌入引起的异常也有一定效用,毕竟正常用户较少会频繁触发异常。

总之,通过这个方案,我们可以非常有效的实时监控异常波动,较少误报。

p.s. 这个想法是早上洗澡的时候想到的,还没开始实践。但是我信心满满写下这篇,数据会来证明我是对的。

异常消息国际化与规范化

目前各浏览器的实现都是:

  • 根据用户设定的方言不同,抛出的 JavaScript 异常消息也会被本地化。
  • 不同浏览器对于同一个异常抛出的消息不同。

而实际上,这种本地化和差异化可以说并非有必要,甚至是有害的。这导致 JavaScript 异常很难被统一进行统计,尤其是针对国际化市场的网站。

是否可以推进 w3c 组织来规范化 JavaScript 异常,让每个异常都是统一一致的。

saijs/sai.js#41

讨论:异常评分算法

为了便于评估 JavaScript 异常的等级,发现最严重的异常,我们需要一套评分算法,来计算每个异常的等级。

异常率

最早的时候,前端监控系统提出了简单的『异常率』概念,定义如下:

异常率(PV) = 异常PV / 所在页面PV;
异常率(UV) = 异常UV / 所在页面UV;

这个初步想来好像没有什么问题,而且经过简单的数据分析及资源异常的相关经验,还搞了一个很龟毛的算法:

异常率为千分之 2 的 N 次方,则异常等级为 N

例如:异常率为 4‰ 的异常是 2 级异常。对于小数部分有另外的处理,最高 10 级异常。
并从资源异常数据的分析经验和脚本异常本身的粗略分析得出,普遍异常率在 2‰ 左右小幅度波动,异常率大于 4‰ 的 2 级异常属于需要关注的异常。

但是近期经过深入的实际数据分析发现,需要考虑的实际情况要复杂的多。

异常评分算法

实际影响异常等级的因素包括:

  • 页面 PV 等级。
  • 页面 UV 等级。
  • 异常 PV 等级。
  • 异常 UV 等级。
  • 异常率 PV(同上面的定义)。
  • 异常率 UV(同上面的定义)。
  • 异常消息:对异常消息进行等级划分,有些异常具有明显的特征,另外一些则有较多的不确定性。
  • 异常文件:本地文件予以忽略,页内脚本异常不确定较高,外联脚本的异常则更稳定。
  • 异常行号也有部分影响,比如超大行号。
  • 客户端环境:部分异常在特定客户端环境场景下异常率较高,但可能会被其他基数较大的客户端环境拉低。

影响隐私较多,目前还没有确定评分算法,请算法大牛,数学怪才多多赐教。

参考

每周异常:第 8期,异常象限

上周的 每周异常:第 7期,保持简单,保持拙朴
中介绍了通过页面关键的 URL/ID 信息,简单有效的发现异常的方案。
本周我们在这个异常分析方法之上,再稍微深入下:

对于整个网站来说,每个系统、页面是有重要级别的,比如支付宝收银台就是 A1 级系统。
这些系统、页面一般可以按照以下方法来判断重要性:

  • 访问量极大,是最主要的业务,对用户影响极大。
  • 用户本身重要(例如商户,尤其是大商户),是最重要的业务,对企业影响巨大。

对于前者,我们可以通过系统、域名的访问量来近似的划分;
对于后者,我们可以建立白名单机制。

对于异常来说,可以通过异常率来判断异常自身的紧急程度。


  • 系统、页面重要性作为『异常重要性』的一个指标。
  • 异常率及异常量作为『异常紧急程度』作为一个指标。
    • 异常率超过阈值的为高发率异常。
    • (可选)异常量超过阈值的也是高发量异常。
    • 并且以高发率为主。

我们画出以下异常象限:

quadrant

  • 图中一、二、三、四为象限代号。
  • (0, 1, 2, 3) 为异常重要性序号。

经过一些分析,我们可以得出结论:

  • 第一、第二象限的异常,是我们最需要关注的部分。尤其是第一象限的异常,
    实时高发异常一般也是在这个象限。
  • 第四、三象限的低发异常,则几乎可以不用关注。
  • 总之我们只需要关注高发异常,但应该以页面的重要性来排优先级。

如何设定阈值?

要确定异常象限,确定各个阈值是关键,这里只提供一些简单的参考。

  • 重要程度:全站重要页面占全站所有页面的 5% 左右,可以根据自身情况调整。
  • 紧迫程度:
    • 目前的经验数据是异常率 4‰ 以上可以认为是高发率异常,但仍需继续
      分析实际数据做调整。
    • (可选)异常 PV 超过 (全站访问最高页面 PV 乘以 1‰) 的也可以认为是高发量异常,
      实际可以根据自身情况调整。

JavaScript 异常文档计划

4月份计划

  • 整理异常文档样本
  • 与集团其他子公司同学交流。(淘宝@云谦,一淘@基德,ICBU@王一)
  • 迁移现有收集到的100多条异常文档。

5月份计划

  • 推广,让更多人一起来参与交流、维护与分享。

讨论:主动抛出的异常消息使用特定的标识

为了避免主动抛出的异常和浏览器自动抛出的异常混淆,以利于异常的分析,建议主动抛出的异常消息附加特定的标识。

例如添加前缀:

throw new Error( ERROR_PROFIX + error_message );

标识的规范还没有想好,大家一起来讨论吧。

每周异常:第 2 期 (20130722)

这是全站排名第 4 的一个异常,详情如下:

KEY VALUE
URL https://accounts.alipay.com/console/selectStrategy.htm
File https://a.alipayobjects.com/??seajs/1.3.1/sea.js,seajs/1.3.1/plugin-combo.js,jquery/jquery/1.7.2/jquery.js
Line 35
Message 找不到成员。
Stack Trace Device OS Browser Engine Count
at function(a,b,d) pc/-1 windows/6.1 ie/10.0 trident/6.0 826
at function(a,b,d) pc/-1 windows/6.1 sg/2.x trident/6.0 423
at function(a,b,d) pc/-1 windows/6.2 ie/10.0 trident/6.0 151
at function(a,b,d) pc/-1 windows/6.1 baidu/2.x trident/6.0 27
at function(a,b,d) pc/-1 windows/6.1 mx/3.4.5.2000 trident/6.0 10
at function(a,b,d) pc/-1 windows/6.1 tw/3.6.1.1 trident/6.0 10
at function(a,b,d) pc/-1 windows/6.2 mx/4.0.6.2000 trident/6.0 3
at function(a,b,d) pc/-1 windows/6.1 lb/undefined trident/6.0 2
at function(a,b,d) pc/-1 windows/6.1 mx/3.5.2.1000 trident/6.0 2
at function(a,b,d) pc/-1 windows/6.1 mx/4.0.5.3000 trident/6.0 2
at function(a,b,d) pc/-1 windows/6.1 mx/4.0.5.4000 trident/6.0 2
at function(a,b,d) pc/-1 windows/6.1 mx/4.0.6.2000 trident/6.0 2
at function(a,b,d) pc/-1 windows/6.1 qq/7.3.11251.400 trident/6.0 2
at function(a,b,d) pc/-1 windows/6.2 tw/-1 trident/6.0 2
at function(a,b,d) pc/-1 windows/6.1 360/3.7.1.6 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 360/4.0.3.6 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 360/4.0.5.2 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 mx/2.5.15.1000 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 mx/2.5.16.100 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 mx/4.0.3.6000 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 mx/4.0.5.2000 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 mx/4.1.0.2000 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 qq/6.13.13719.201 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 qq/6.14.15493.201 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 tw/3.4.0.5 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.1 tw/3.6.1.0 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.2 mx/3.5.2.1000 trident/6.0 1
at function(a,b,d) pc/-1 windows/6.2 mx/4.0.5.3000 trident/6.0 1
1478

首先我们找到异常所在文件的第 35行,在上面的异常栈信息中我们可以知道异常从
压缩后的匿名函数 function(a,b,d) 中抛出,这个函数是:

set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}

对应压缩前的源码是:

    // Use this for any attribute in IE6/7
    // This fixes almost every IE6/7 issue
    nodeHook = jQuery.valHooks.button = {
        get: function( elem, name ) {
            var ret;
            ret = elem.getAttributeNode( name );
            return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
                ret.nodeValue :
                undefined;
        },
        set: function( elem, value, name ) {
            // Set the existing or create a new attribute node
            var ret = elem.getAttributeNode( name );
            if ( !ret ) {
                ret = document.createAttribute( name );
                elem.setAttributeNode( ret );
            }
            return ( ret.nodeValue = value + "" );
        }
    };

这个代码是用来兼容 IE6/7 的属性操作(代码注释中号称几乎修复了 IE6/7 所有的问题),
页面中的业务源码中我们找到不少 DOM 属性的操作,其中有一些看起来几乎不会有问题,
另外一些则出现问题的可能性更高。

我们从这些可能性更高的部分着手,最终发现 IE10 兼容模式下会抛出『找不到成员。
的异常,这时我们注意到,异常详情中全部都是 Trident 6.0 内核的浏览器抛出的异常。
看来我们这个增加兼容模式信息的 Issues#13
是正确的。

我们看到兼容模式下表单中的 Select 模拟组件显示不正常,最初以为是 Select 组件设置属性造成的异常。

1c00b54eaad3a6f45ffec0d7526abc6e

(正常情况下的 Select 模拟组件)

0aafc43da07d07423e1f67199cf5c5b9

(兼容模式下的 Select 模拟组件)

IE10 的调试工具还是不错的,最终我们发现在设置 "novalidate" 属性时抛出这个异常,
想到 Validator 的相关 Issues#30

edde87186b5919bd49a4d74874c7ce36

于是这个异常就破解完成。

如何修复这个异常?

找回密码的这个页面使用的 [email protected],而 novalidate 属性造成的异常在
[email protected] 及其后续版本中得到修复。建议升级到最新版本,如果升级过程中
遇到问题,请咨询组件负责人 @乔花。

JavaScript 异常:错误的参数个数或无效的参数属性值

脚本 A 上线后,脚本 B 中出现了大量的 JavaScript 异常:『错误的参数个数或无效的参数属性值』
主要在 IE 系列浏览器中,尤其以 IE8 等为主。

分析发现:

  • 脚本 A 在页面加载时创建了 DOM 表单元素,并进行了 form.submit() 操作。
  • 脚本 B 在页面加载到底部时,进行了修改 DOM 元素的属性操作。

但是本地自己的浏览器没有能够重现异常。

后来脚本 A 修改了执行时机,在 DOM Ready 之后操作 DOM,异常消失。


『错误的参数个数或无效的参数属性值』这个异常在网络上以 ASP, VBScript,ActionScript,PHP 等类型的语言中反馈较多。

JavaScript 语言对函数参数没有约束,无论是参数个数,或者参数值,都不会有异常抛出。
但是内置的 DOM 操作函数除外,他们一般有参数类型检查,传入非预期的参数或抛出异常。

另外,由于本地未能重现,说明有异常概率。有相关文章提供了因为系统动态链接库(DLL)问题导致这个异常发生,甚至影响到 Windows 系统控制面板的显示,可以通过运行重现注册动态链接库解决问题。

参考

每周异常:第 3期,宏观异常分析方法 (20130802)

为了有效的分析 JavaScript 异常,我们从宏观的统计数据中做了大量的分析,初步结果如下:

全站每日异常统计

首先我们从每天的全站总体数据进行分析,得到下面这些报表。其中:

  • all_pv: 全站所有有效异常次数 (PV)
  • all_uv: 全站所有有效异常人数 (UV)
  • all_pv_rate: 全站所有异常率 (按 PV 统计)
  • all_uv_rate: 全站所有异常率 (按 UV 统计)
  • valid_rate_pv: 有效异常率(按 PV 统计)
  • valid_rate_uv: 有效异常率(按 UV 统计)

2013-08-02 7 42 02

2013-08-02 7 42 37

2013-08-02 7 50 11

2013-08-02 7 43 04

2013-08-02 7 43 31

从趋势图中我们可以明显的看到第 2天和最后一天,异常 PV 有较大波动,这是由于用户攻击爆发大量异常造成的。

我们在 异常评分算法 的讨论中有提到,即使是少量用户进行攻击,异常 UV 也不应该有波动的(除非页面 UV 本身有较大波动),但是实际上我们看到最后一天的异常 UV 也有明显波动,因此粗略分析这个异常 PV 的上升是由于页面访问人数增多造成的。

事后我们查了当天的页面访问 PV 和 UV,可惜并没有明显波动。

实际经过细致的异常日志分析,我们看到攻击用户同时没过一段时间就更换 IP 地址或清理 Cookie,因此全天的综合数据中异常 PV 和异常 UV 有了一些明显波动。

而且实际上,每天都有攻击造成的异常波动。

  • 每天都有攻击造成的异常,累加在一起好像每天都没什么问题。
  • 由于攻击造成异常总数相对一天的异常总数太小,从全天异常总数的层面来看,无法发现,甚至排除攻击造成的异常。
  • T+1 的统计数据,对于异常监控来说,太迟钝了。

全站每分钟异常统计

从上面每日异常总和的分析我们可以看出,一天的总和数据能发现的问题还是比较有限的。于是我从分钟的粒度来统计异常。

2013-08-02 7 24 54

图有点大,点击可以看大图。

从图中我们可以看到,凌晨 2点多到 3点多期间,有少量用户发起了猛烈的攻击;凌晨 4点多到凌晨7点多期间也有少量温和的攻击。攻击时全站异常 PV (绿色)和全站有效异常(深红色)明显飙升,但是异常 UV 和有效异常 UV 都没有太大波动,而且与前后的时间切片有较好的连贯性,没有大起大落。

而如果异常 UV 出现较大波动,在很可能是由于我们正在发布的新代码影响大面积用户。

合适的时间粒度对于及时发现真正有效的异常有非常重要的意义。

每周异常:第 6期,JavaScript 异常监控脚本引发的 JavaScript 异常 (20130822)

背景

早上收到比较重要的用户反馈说某个重要的系统的页面在 IE 浏览器中卡死,无法继续。

2013-08-22 5 14 01
2013-08-22 5 14 25

问题很严重。

系统比较特殊复杂,不是一般人能登录进去的。找到对应同学在小概率重现的情况下终于拿到了第一手的问题源码。

分析

修改配置和采样率之后在本地跑起来,发现在 IE6 里非常有问题。

  1. 性能这么大的问题,首先想到的是页面中大面积扫描 HTML 代码的部分,但是注释掉这部分仍然没有改善。

  2. 再次把整个回调处理函数注释掉,仍然没有改善。

  3. 直接把加载后置脚本的 seajs.use 注释掉之后,终于没问题了。(坑爹的缓存问题就不说了,好久之后才发现,都是泪~)

  4. 这么看来有两种可能:

    1. 模块本身有性能问题。
    2. seajs 性能有问题(这个问题页面目前使用的 1.3.1)
  5. 首先考虑自身的问题。return 整个 factory,性能无问题,因此不是模块过多导致 seajs 性能问题。

  6. 逐步修改 return 位置(其实可以打断点),终于发现问题出在 初始化事件绑定函数部分

    11.  // 避免未引用先行脚本抛出异常。
    12.  if(!win.monitor){
    13.    M = win.monitor = {};
    14.    M._DATAS = [];
    15.    M._EVENTS = [];
    16.  }
    17.
    18.  var _events = M._EVENTS;
    19.  var _evt = new Events();
    20.  M.on = function(evt, handler){
    21.    _evt.on(evt, handler);
    22.  };
    23.  for(var i=0,l=_events.length; i<l; i++){
    24.    M.on(_events[i][0], _events[i][1]);
    26.  }
  7. 此时 _events = undefined。访问 undefined.length 或其他属性会导致浏览器挂起? IE6 还有这样的坑?

  8. 新建一个最小代码的新页面,直接 script[src] 引入这个脚本没有问题,IE 会正常的抛出异常,不会挂起。

  9. 动态创建 script 插入脚本,仍然不会挂起,正常抛出异常。

  10. 奇怪。各种尝试,最终加入前置脚本后在这个最小重现代码中重现了异常。

  11. 然后发现是在 window.onerror 中出现问题。

  12. 继续发现的模拟调用栈信息的算法出现问题。

cmd 模块在 define 的 factory 中本身抛出异常,会被前置脚本的 window.onerror 捕获,捕获过程中尝试 还原函数调用栈。模拟还原调用栈信息是通过 arguments.callee.caller 向上递归,直到找到函数调用的发起者。但是悲剧的是发现 seajs 这个调用栈是无穷无尽的:

at function(require, exports, module)
at function runInModuleContext(fn, module)
at function()
at function(uri)
at function(item, i, arr)
at function(arr, fn)  ........-
at function(arr, fn)          |
at function()                 |
at function preload(callback) |
at function()                 |
at function cb(module)        |
at function onFetched()       |
at function(fn)       ........-
at function(arr, fn)    ..........-
at function(arr, fn)              | 循环往复
at function()                     |
at function preload(callback)     |
at function()                     |
at function cb(module)            |
at function onFetched()           |
at function(fn)         ..........-
at function(arr, fn) .........-
at function(arr, fn)          | 无穷尽也
at function()                 |
at function preload(callback) |
at function()                 |
at function cb(module)        |
at function onFetched()       |
at function(fn)         ......-

这也就是为什么用户的浏览器会挂起的原因。


小结

用户浏览器被挂起,最终发现:

  • 不是扫描整个文档的性能问题。
  • 不是 seajs 加载多个模块的性能问题。
  • 主要不是 cmd define 中报错的问题。
  • 不是 window.onerror 捕获异常的问题。
  • 而是获取异常函数调用栈出现无限循环链的问题,
  • 这其实是 IE 的问题。
  • 好吧,最终还是我的问题。

监控脚本自身的 cmd 模块定义的异常是怎么出现的?

monitor 2.2.0 新增了 事件机制,支持监控任意的自定义数据。

其中在前置脚本中新增了 monitor.on() 方法,将自定义事件处理函数临时存储在 monitor._EVENTS 属性中 参考代码 。后置脚本加载完成后,会复写 monitor.on() 方法,并将之前用户自定义事件处理函数绑定到对应事件,以使事件机制生效。

这些理论上都是非常不错的设计思路。

但是现实太残酷,全站公共区域被实际部署的情况有太复杂:

  1. 有些系统没有完全引入前置脚本。
  2. 有些系统仍然引用的老版本的前置脚本。

后置脚本对于后面的这个场景没有考虑到,因此这段保险栓未能正确执行导致后续脚本报错。

为什么这个异常会到生产环境?

都是我的错,没有考虑周全。没有考虑到这种特殊的系统部署环境,而且我也从来不能进去实际体验、验证。

如何处理这个异常?

  1. 作为全站全局运行的代码,要详细评估考虑到各种系统变态的部署环境。
  2. 打断循环调用栈
    1. 限制监控的最大函数调用栈深度。
    2. 对于已经调用的栈,不再深入递归,或现在递归次数(待评估,实际项目存在正常的递归或多次调用的代码。)

为什么 seajs define 模块中报错会出现函数调用栈无限递归?

这是 seajs 1.3.1 的 BUG,更准确的说,可能是 IE6,7,8闭包的问题。参考: seajs/seajs#911

每周异常:第 5期,由静态资源异常引发的质疑 (20130816)

今天要说的是一个『静态资源异常监控』的话题。虽然静态资源异常可以非常有效的跟 JavaScript 异常结合起来做有力的分析,但是今天这个话题跟 JavaScript 异常没什么关系。


传说某著名公司的网站监控方案是申请了专利的技术,我有幸看到过,真的是非常牛逼。

这个网站监控方案中提供了网页的访问量、页面访问流向、页面有效点击量、点击热图,灰度发布(包括 ABTest)等都有完善的支持。

这个方案总体上是用 JavaScript 通过 new Image() 发送监控的数据到日志服务器,然后由数据仓库做数据清洗。

通过业务系统服务器端的日志分析,这套方案的数据质量有确实有一定的问题,比如它统计到的访问量要比业务系统服务器端统计到的少,丢失率一般在 5% ~ 10% 左右,少量特殊页面(比如跳转页)丢失率会高达 50% ~ 60%。

尽管如此我们也基本上可以接受这样的误差,而且部分从理论上讲,『应该』大部分是无效或无意义的访问(比如爬虫、快速跳转等),丢掉是合理的。

一直以来,我们都认为这个方案的数据是相对比较可靠的,虽然偶尔有些不正常的丢失率让业务方质疑,但是我们总是用上面的理论和理由解释给对方听,让他们接受我们的现实。

这样过了好多年,他们过着幸福的生活~


直到有个家伙的膝盖被射了一箭,搞出来个静态资源监控系统。

静态资源监控系统是通过分析静态服务器的访问日志,来统计各个资源状态的趋势及其分布情况。

因为静态资源监控是通过静态服务器访问日志来分析的,理论上和业务服务端的日志是一致的。

但是我们惊奇的发现某个静态页面引用了某个不存在的静态资源,异常率理论上应该是 100%,但是对照网站监控方案统计的数据发现,这个静态资源异常率高达 200%。

我的第一直觉是网站监控丢失率高达 50%。遂跟网站监控相关同学沟通,然后他们第一反映是质疑我对他们的质疑。

让业务服务端同学统计日志发现他们的访问量确实要大于网站监控访问量,但又小于静态资源异常量。

我虽然毫不怀疑自己的数据,但一时也无法质疑业务服务端的数据。

业务服务端同学随意说了一句:『难道有缓存?』

于是这个疑惑破解了:

  1. 用户访问页面时,有一部分可能是读取的本地缓存,这部分业务服务端无法统计到。
  2. 有些读取缓存的场景不会执行初始化 JavaScript,这些场景网站监控没有统计到。
  3. 外联的 404 资源是不会被缓存的。

因此这些有缓存的静态页面(这个知名公司有不少页面是这种纯静态、伪静态页面)的场景下,使用 JavaScript 发送监控的监控手段目前丢失率是 50% 左右。

参考

搜狗浏览器 2.x (很多版本都使用这个版本号)居然还进行主动 XSS 攻击,恶心到了

今天排查一个异常的时候,看到有这样一个异常

搜索了一下,发现这些:


搜狗浏览器在使用 Trident 内核时,在页面中插入 XSS 脚本:

<script type="text/javascript" id="tailjs-19055818_3104" sogou-script="true" src="https://BCC0E825-2420-4190-AF25-ABD45D41EA3A/sb/exttailcontentscript/?sbid=tailjs-19055818_3104&isTopFrame=true&url=http%3a%2f%2fllshz.XXXXXX.net%2findex24.asp" charset="UTF-8"></script> 

每周异常:第 2 期(续)(20130723)

上篇 分析了上周的异常 TOP4,顺便发现 TOP3 和 TOP5 是同一个问题造成的异常。

下面我们来分析 TOP1 和 TOP 2 的异常,事实上,这两个异常也是由同一个问题造成的。

TOP1

KEY VALUE
URL https://memberprod.alipay.com/account/reg/complete/complete.htm
File https://a.alipayobjects.com/??seajs/1.3.1/sea.js,seajs/1.3.1/plugin-combo.js,jquery/jquery/1.7.2/jquery.js
Line 35
Message 无法获取未定义或 null 引用的属性“length”
stack trace device os browser engine count
at function(a,c,d) pc/-1 windows/6.1 ie/10.0 trident/6.0 2740
at function(a,c,d) pc/-1 windows/6.1 sg/2.x trident/6.0 868
at function(a,c,d) pc/-1 windows/6.2 ie/10.0 trident/6.0 538
at function(a,c,d) pc/-1 windows/6.1 tao/3.2 trident/6.0 236
at function(a,c,d) pc/-1 windows/6.1 baidu/2.x trident/6.0 113
at function(a,c,d) pc/-1 windows/6.1 tw/3.6.1.1 trident/6.0 36
at function(a,c,d) pc/-1 windows/6.2 tao/3.2 trident/6.0 26
at function(a,c,d) pc/-1 windows/6.1 mx/4.0.5.2000 trident/6.0 25
at function(a,c,d) pc/-1 windows/6.1 mx/4.1.0.2000 trident/6.0 20
at function(a,c,d) pc/-1 windows/6.1 mx/4.0.6.2000 trident/6.0 15
at function(a,c,d) pc/-1 na/-1 ie/10.0 trident/6.0 14
at function(a,c,d) pc/-1 windows/5.0 ie/10.0 trident/6.0 12
at function(a,c,d) pc/-1 windows/6.1 mx/4.1.1.600 trident/6.0 10
at function(a,c,d) pc/-1 windows/6.1 tw/3.6.1.0 trident/6.0 10
at function(a,c,d) pc/-1 windows/6.1 mx/4.0.3.1000 trident/6.0 8
at function(a,c,d) pc/-1 windows/6.2 mx/4.0.6.2000 trident/6.0 8
at function(a,c,d) pc/-1 windows/6.1 tao/3.0 trident/6.0 7
at function(a,c,d) pc/-1 windows/6.1 mx/3.4.5.2000 trident/6.0 6
at function(a,c,d) pc/-1 windows/6.1 qq/6.14.15493.201 trident/6.0 6
at function(a,c,d) pc/-1 windows/6.1 qq/7.3.11251.400 trident/6.0 6
at function(a,c,d) pc/-1 windows/5.1 ie/10.0 trident/6.0 4
at function(a,c,d) pc/-1 windows/6.0 ie/10.0 trident/6.0 4
at function(a,c,d) pc/-1 windows/6.1 360/3.2.0.6 trident/6.0 4
at function(a,c,d) pc/-1 windows/6.1 lb/3.1.15.3877 trident/6.0 4
at function(a,c,d) pc/-1 windows/6.1 mx/3.5.2.1000 trident/6.0 4
at function(a,c,d) pc/-1 windows/6.1 mx/4.0.5.4000 trident/6.0 4
at function(a,c,d) pc/-1 windows/6.2 tao/3.1 trident/6.0 4
at function setCaretToInput(input)at function(event)at function(a,c,d) pc/-1 windows/6.1 sg/2.x trident/6.0 3
at function(a,c,d) pc/-1 windows/6.2 tao/3.0 trident/6.0 3
at function(a,c,d) pc/-1 windows/6.1 qq/7.3.8581.400 trident/6.0 2
at function setCaretToInput(input)at function (event)at function(a,c,d) pc/-1 windows/6.2 ie/10.0 trident/6.0 2
at function(a,c,d) pc/-1 windows/6.1 qq/6.14.14517.201 trident/6.0 1
at function setCaretToInput(input)at function (event)at function(a,c,d) pc/-1 windows/6.1 sg/2.x trident/6.0 1
4744

TOP2

KEY VALUE
URL https://memberprod.alipay.com/account/reg/index.htm
File https://a.alipayobjects.com/??seajs/1.3.1/sea.js,seajs/1.3.1/plugin-combo.js,jquery/jquery/1.7.2/jquery.js
Line 35
Message 无法获取未定义或 null 引用的属性“length”
stack trace device os browser engine count
at function(a,c,d) pc/-1 windows/6.1 ie/10.0 trident/6.0 2192
at function(a,c,d) pc/-1 windows/6.1 sg/2.x trident/6.0 1255
at function(a,c,d) pc/-1 windows/6.2 ie/10.0 trident/6.0 352
at function(a,c,d) pc/-1 windows/6.1 baidu/2.x trident/6.0 143
at function(a,c,d) pc/-1 windows/6.1 tw/3.6.1.1 trident/6.0 42
at function(a,c,d) pc/-1 windows/6.1 mx/4.0.6.2000 trident/6.0 33
at function(a,c,d) pc/-1 windows/6.1 tw/3.6.1.0 trident/6.0 32
at function(a,c,d) pc/-1 windows/6.1 tao/3.0 trident/6.0 13
at function(a,c,d) pc/-1 windows/6.2 tw/3.6.1.1 trident/6.0 13
at function(a,c,d) pc/-1 windows/6.1 mx/3.4.5.2000 trident/6.0 11
at function(a,c,d) pc/-1 windows/6.1 qq/7.3.8126.400 trident/6.0 9
at function(a,c,d) pc/-1 windows/6.2 tao/3.0 trident/6.0 9
at function(a,c,d) pc/-1 windows/6.1 360/3.9.1.5 trident/6.0 6
at function(a,c,d) pc/-1 windows/6.1 mx/3.4.1.1000 trident/6.0 6
at function(a,c,d) pc/-1 windows/6.1 mx/4.0.3.3000 trident/6.0 6
at function(a,c,d) pc/-1 windows/6.1 qq/6.14.14517.201 trident/6.0 6
at function(a,c,d) pc/-1 windows/6.1 qq/6.14.15493.201 trident/6.0 5
at function(a,c,d) pc/-1 windows/6.1 360/3.2.0.2 trident/6.0 4
at function(a,c,d) pc/-1 windows/6.1 qq/7.3.11251.400 trident/6.0 4
at function(a,c,d) pc/-1 windows/6.1 360/3.1.6.7 trident/6.0 3
at function(a,c,d) pc/-1 windows/6.2 sy/-1 trident/6.0 3
4147

TOP1 和 TOP2 两个异常都是中 memberprod 系统的注册页面上(重要程度不想而知),
一个页面是注册的起始页,一个是注册的完成页。我们先分析起始页。

打开异常所在文件第 35行,搜索异常栈信息中的 function(a,c,d),我们发现有 3 个如下:

each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a}
map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)}
prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}}

再找异常信息中的关键字 length,发现上面的 prop 方法中并没有这个关键字,
所以可以排除。剩下的两个函数对应的原始码如下:

// args is for internal usage only
each: function( object, callback, args ) {
  var name, i = 0,
      length = object.length,
      isObj = length === undefined || jQuery.isFunction( object );

  if ( args ) {
    if ( isObj ) {
      for ( name in object ) {
        if ( callback.apply( object[ name ], args ) === false ) {
          break;
        }
      }
    } else {
      for ( ; i < length; ) {
        if ( callback.apply( object[ i++ ], args ) === false ) {
          break;
        }
      }
    }

    // A special, fast, case for the most common use of each
  } else {
    if ( isObj ) {
      for ( name in object ) {
        if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
          break;
        }
      }
    } else {
      for ( ; i < length; ) {
        if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
          break;
        }
      }
    }
  }

  return object;
},

// arg is for internal usage only
map: function( elems, callback, arg ) {
  var value, key, ret = [],
    i = 0,
    length = elems.length,
    // jquery objects are treated as arrays
    isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;

  // Go through the array, translating each of the items to their
  if ( isArray ) {
    for ( ; i < length; i++ ) {
      value = callback( elems[ i ], i, arg );

      if ( value != null ) {
        ret[ ret.length ] = value;
      }
    }

  // Go through every key on the object,
  } else {
    for ( key in elems ) {
      value = callback( elems[ key ], key, arg );

      if ( value != null ) {
        ret[ ret.length ] = value;
      }
    }
  }

  // Flatten any nested arrays
  return ret.concat.apply( [], ret );
},

这两个函数都有可能性,但是从经验上 each 方法用的更多,我猜这个命中率更高。
但是单纯的从这些异常信息字面量来看,已经没有了更多信息。我们当时还没有注意到
异常都是 Trident 6.0 抛出的。


打开页面标准模式或兼容模式下都没有抛出这个异常,只是兼容模式下有个
『找不到成员。』的异常。

手贱点了表单第一个输入框,终于重现了梦寐以求的异常。它就是 each 方法中抛出的。
从调用堆栈信息中可以看到 this.items 是 undefined,导致 each 失败。

2013-07-23 9 12 58

this.items 为 undefined 是由于 validator 的 core 模块中 setup() 函数中
设置 novalidator 属性异常导致后续代码没有执行造成。

setup: function() {

  //disable html5 form validation
  this.element.attr('novalidate', 'novalidate');

  //Validation will be executed according to configurations stored in items.
  this.items = [];

  //...

}

至此,上周支付宝全站 TOP5 的异常均已告破,他们都是同一个问题造成的。

如何解决这个异常?

Validator 组件已于 3个月前的 v0.9.2 版本得到修复,请升级到 Validator 最新版。
升级过程中遇到问题,请联系 @乔花。

一个问题?

基础组件中小版本升级修复各种 BUG 的过程中,如何同步推动业务代码的对应升级?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.