GithubHelp home page GithubHelp logo

sai.js's Introduction

Sai: 通用前端监控采集脚本。


spm package Build Status Coverage Status

Sai 是《棋魂》中追求神乎其技的魂,千年来他隐身身后,为追求神乎其技孜孜不倦。

sai

概述

整体上,前端监控包含很多方面,例如包括:

  • JavaScript 异常监控
  • 敏感信息监控
  • DOM 合法性检查
  • 静态资源监控
  • 网站监控等

为了更好的扩展性,和结构上的简单清晰,前端监控核心模块提供了基础的数据交换接口。 上层的监控模块负责各自独立的监控逻辑,并通过这个接口传输监控数据。

大致结构如下图:

前端监控组件结构

从上图看,前端监控(Sai 模块)提供了核心的 log() 接口, 和 on(), off() 事件机制,其中 log() 接口提供了可扩展的数据传输方案, on(), off() 事件机制提供了简单易用的扩展能力。

JSniffer 模块提供了全局的 JavaScript 异常监控支持,并扩展了 error() 方法。 为开发者监控主动捕获的异常做支持。

JSniffer 同时还扩展了 lost() 方法,在外部模块或资源加载失败时调用 lost() 方法,监控到 JavaScript 异常时,附加这些缺失的资源信息,辅助异常分析。

JavaScript 异常是前端监控中最重要、最常用的信息,同时为了简化 API,所以将 这两个接口扩展附加在 Sai 模块上。

其他的 DOMLint, SENS 等第三方独立的监控模块,都直接调用 Sai.log() 接口 传输数据。

安装

前端监控脚本拆分为两个部分:

  • seer.js

    小巧的先行脚本建议内联(也可以外联)在页面头部,在所有脚本和外部资源之前。 用于收集全局的异常,并为后续业务准备好可用的 API。

  • sai.js

    后置监控模块(这是一个 CMD 模块)可以通过异步方式加载在页面底部, 用于处理监控日志的发送和其他扩展支持。

<html>
  <head>
    <meta charset="utf-8" />
    <script type="text/javascript" src="dist/seer.js"></script>
  </head>
  <body>

  page content...

  <script type="text/javascript">

    // 如果没有 file 等协议场景,可以直接使用 `//log.example.com/sai.gif`。
    var protocol = String(loc.protocol).toLowerCase();
    if(protocol !== "https:"){ protocol = "http:"; }
    if (window.Sai) {
      Sai.server = protocol + "//log.example.com/sai.gif";
    }

    seajs.use("sai", function(Sai){
    });

  </script>
  </body>
</html>

使用说明

一般情况下,JavaScript 异常监控前置脚本已经自动收集了页面上抛出的异常, 但也有部分场景,业务逻辑中 catch 捕获住了抛出 JavaScript 异常,避免影响后续 的业务逻辑,但同时希望监控到这个异常场景,可以主动调用 Sai.error() 接口:

try{
  throw new Error("msg");
}catch(ex){
  Sai.error(ex);
}

API

Sai.error(Error error)

JavaScript 异常监控的接口,可以用于主动监控被捕获的 JavaScript 异常。

Sai.log(Object seed [, String profile])

前端监控的通用频次监控接口。通过这个发送监控数据,并配合对应的日志处理和数据分析, 可以完成多种监控需求。

  • seed: 详情数据,可以是简单的字符串,或者 key: value 键值对对象。
  • profile: 日志类型,默认为 log

Sai.on(String eventName, Function handler)

监控到特定类型的数据时,会触发的特定事件。内置支持的事件类型包括:

  • *: 发送所有类型的数据都会触发。
  • jserror: 发送 JavaScript 异常数据前触发。
  • log: 发送自定义 log 监控数据会触发。
  • 其他任意的自定义类型类似。

Sai.off(String eventName [, Function handler])

取消通过 on 绑定的事件。

Sai.lost(String uri)

页面加载特定资源失败时,可以调用这个方法。 缺失的资源对于异常分析有较大帮助。

范例:

<script src="sea.js" onerror="window.Sai && Sai.lost && Sai.lost(this.src)"></script>
<script>
// seajs 2.1 开始支持,但 error 事件仍有缺陷,seajs 2.2 的 error 事件较适合本场景。
seajs.on("error", function(module){
  window.Sai && Sai.lost && Sai.lost(module.uri);
});
</script>

sai.js's People

Contributors

hotoo avatar

Stargazers

 avatar Silent Lee avatar Albert 理斯特 avatar yaojia avatar nebel avatar zhishaofei avatar  avatar oxxd avatar Xiaoyu Zhang avatar jarzzzi avatar Reyn avatar fengjutian avatar 李森林 avatar Objectivezt avatar  avatar Garen avatar 李墨 avatar  avatar Tong avatar  avatar NuoHui avatar Howie Wong avatar 深雨 avatar Addy Zhou avatar  avatar JSupot avatar  avatar shadow avatar John Mai avatar xiaowang avatar Jin avatar anitakym avatar 飝猫 avatar zifan avatar Jeff Li avatar Vic avatar Spring Swordsman avatar Hanran avatar  avatar  avatar  avatar Świstak avatar  avatar  avatar uxyheaven avatar fairy avatar Kaijun Chen avatar 云峰 avatar Zijor avatar zhou avatar 淡漠 avatar  avatar szlanyou.com avatar LDQ-first avatar LIUTX avatar 麻辣石头 avatar Ben avatar Reno avatar Li Run Kai avatar meoww-bot avatar 1dao avatar  avatar  avatar 志遥 avatar 暖风 avatar Shen avatar M avatar Frank avatar Vinne avatar 风知 avatar  avatar Jimmie avatar elsa avatar  avatar TroyMa avatar 默凡 avatar peng avatar 江小白 avatar RENKUN avatar  avatar  avatar Chaos(拾锋) avatar 王超 avatar 胡继伟 avatar Yu Hai avatar Shangbin Yang avatar skyvow avatar Aresn avatar allen avatar  avatar 寒潭清 avatar Zhifeng Li avatar sdvcrx avatar DEDEDE avatar Kai avatar storyflow avatar Ken Huang avatar Router avatar 流采 avatar 书越 avatar

Watchers

 avatar James Cloos avatar MIC avatar diycp avatar centiyo avatar pokerfive avatar 胡继伟 avatar  avatar  avatar

sai.js's Issues

异常消息国际化与规范化

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

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

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

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

采样率调整

  • 敏感信息监控采样率保持 1%
  • 异常监控采样率 10%

收集客户端语言信息

在 IE 浏览器中不同用户语言环境抛出的异常消息不同,虽然有将异常消息统一使用英文的提案,但目前来说,收集语种信息,对于识别异常消息非常有利。

lang = navigator.language || navigator.browserLanguage;

页内启动脚本改造

收银台等特殊系统对页面性能有较高要求,而且本身的代码性能不佳,可能会在加在页面最底部 use 的 monitor 脚本之后才加载完成并执行,因此考虑将启动脚本改造如下:

function _use_monitor(){
  if(!window.seajs){return;}

  seajs.use("$", function($){
    $(function(){
      // 命中率:[0,1]: 实际对应采样率:[0%,100%]
      // 注意:sens_rate 跟 sens_rate 是有关联的。
      //      实际设置的内容时,应该参考以下算法:
      //      敏感信息监控实际采样率 = sens_rate * jserr_rate;
      var jserr_rate = $MONITOR_JSERROR_RATE;
      var sens_rate = $MONITOR_SENSINFO_RATE;

      /**
       * 随机采样命中算法。
       * @param {Nuber} rate, 采样率,[0,1] 区间的数值。
       * @return {Boolean}
       */
      function hit(rate){
        return 0 === Math.floor(Math.random() / rate);
      }

      if(!hit(jserr_rate)){return;}
      seajs.use(["monitor/2.1.0/monitor", "sensinfo/1.2.0/sensinfo"], 
        function(monitor, sensinfo){

        if(!monitor || !monitor.boot){return;}
        monitor.boot(); // 启动监控。
        if(!hit(sens_rate) || !sensinfo){return;}
        try{
          var html = (document.documentElement || document.body).innerHTML;
          sensinfo.scan(html, {
            "*": function(cardType, privacy_card, context) {
              monitor.log(cardType+"="+privacy_card+"```"+context, "sens");
            }
          });
        }catch(ex){}
      });
    });
  });
}


if(window._monitor_autoload !== false){
  _use_monitor();
}

对于收银台这种可以在前面设置 var _monitor_autoload = false; 并在适当的时机 _use_monitor()

全站全局监控脚本升级注意事项

全站各个系统的部署方式多样,包括以下各个场景,及其各个场景的组合:

  • 残缺:只引入前置脚本,或只引入后置脚本。
  • 老旧:引入的前置脚本和后置脚本版本不一致。
  • 重复:多长引入前置或后置脚本。

方案

  1. 前置脚本中按照现有方案部署。
  2. 并在全局头部文件中做前置脚本的公开方法做兼容,避免业务代码调用出错。
  3. 后置脚本同样做兼容,避免由于前置脚本公开方法和私有属性缺失或老旧造成的异常。

// 未避免业务代码中对 monitor API 调用出错的防护措施,只提供公开方法的防护。
(function(win){
  if(!win.monitor){win.monitor = {};}

  var METHODS = ["lost", "log", "error", "on", "off"];

  for(var i=0,method,l=METHODS.length; i<l; i++){
    method = METHODS[i];
    if("function" !== typeof win.monitor[method]){
      win.monitor[method] = function(){};
    }
  }
})(window);

<script src="sea.js,..."></script>


// 后置脚本同样做好上面的防护。
// 由于头部可能更新不同步,后置脚本的防护性甚至要做到更加全面。
// 包括需要用到前置脚本的所有私有属性、方法。

前端监控计划

4月份计划

  • 资源加载失败的监控技术方案:SeaJS, Loader, SCRIPT.
  • 确定资源加载失败造成的异常处理方案。

研究方向

  • 结合 SourceMap 的思路,展开异常相关源码。难点:异常所在列号。
  • 异常现场场景重现。

删除 boot() 方法

boot() 方法主要为了支持采样监控情况。

新的策略:将采样机制前置,如果采样未命中,则不加载监控后置脚本。

如果监控前置脚本准备妥当,后置脚本加载完成,则启动监控。

考虑提供页面 ID 的配置

  • 在淘宝、ICBU,由于提供个性域名,同一个页面会存在多个 URL 地址。
  • 在支付宝,个人收款主页 下每个用户主页的 URL 地址也不相同,但使用的是同一份源码。

可选方案:

  • 前端方案:monitor.js 中提供设置页面 ID 的接口。
    • 简单:前端实现方案监控,可以保证后端更简单。
  • 后端方案:monitor-agent 中根据 URL 进行自动转换。
    • 目前支付宝个人收款主页就是用的这个方案。
    • monitor.js 保持简单。
    • 但日志分析会比较复杂,而且一旦有新的 URL 规则变化,不能提前自动处理。

新增是否启用全局异常监控的开关

  • 有些情况,比如要嵌入到第三方网站的代码片段(如 Google 和阿里妈妈广告),只希望监控自身特定的脚本异常,不希望监控到这些第三方网站的异常。
  • 全局脚本异常监控会收到很多噪音的影响,为了提高异常的有效性,减少噪音,可能会关闭全局异常监控。

发布 monitor-2.0.0

通过 CMS 平台发布 monitor 脚本,需要 3个 CMS 区域:

  1. seer.js

放置到 seajs 之前:

<script type="text/javascript">
(function(){function k(a){return(a=String(a).match(l))?a[0]:""}function f(a,m,c,f,g){if(!g&&arguments.callee.caller){for(var b=arguments.callee.caller,d=[];b.arguments&&(b.arguments.callee&&b.arguments.callee.caller)&&!(b=b.arguments.callee.caller,d.push("at "+k(b)),b.caller===b););g=d.join("\n")}b={profile:"jserror",msg:a||"",file:m||"",line:c||0,num:f||"",stack:g||"",lost:h.join(",")};e._DATAS.push(b);return b}if(!window.monitor){var e=window.monitor={};e._DATAS=[];var h=[],d={};e.lost=function(a){d.hasOwnProperty(a)||(d[a]=!0,h.push(a))};e.log=function(a,d){if(a){var c=d||"log";3===arguments.length&&(c="product",a=Array.prototype.join.call(arguments,"|"));c={profile:c,seed:String(a)};e._DATAS.push(c);return c}};var l=/^function\b[^\)]+\)/;e.error=function(a){if(a instanceof Error)return f(a.message||a.description,a.fileName,a.lineNumber||a.line,a.number,a.stack||a.stacktrace)};window.onerror=function(a,d,c){f(a,d,c);return!1}}})();
</script>
  1. head
  2. foot
## 前端监控采样率:[0,1] === [0%,100%]
#set($MONITOR_JS_RATE=$velocms2)
<script type="text/javascript">
if(window.seajs){
  seajs.use("$", function($){
    $(function(){
      // 命中率:[0,1]: 实际对应采样率:[0%,100%]
      var rate = $MONITOR_JS_RATE;

      /**
       * 随机采样命中算法。
       * @param {Nuber} rate, 采样率,[0,1] 区间的数值。
       * @return {Boolean}
       */
      function hit(rate){
        return 0 === Math.floor(Math.random() / rate);
      }
      if(!hit(rate)){return;}

      seajs.use(["sai/2.0.0/sai", "sai/2.0.0/privacy"], 
          function(Sai, privacy){

        if(!Sai || !Sai.boot){return;}

        // 启动监控。
        Sai.boot();

        if(!privacy){return;}
        var html = (document.documentElement || document.body).innerHTML;
        privacy.scan(html);

      });
    });
  });
}
</script>

考虑合并发送多个监控数据

考虑到无线设备目前的网络情况,几年内可能不会有巨大的提升,为了能更好的支持无线场景,减少网络请求,考虑合并多个监控数据到同一条请求中发送。

新增列号信息

部分浏览器可以通过间接手段拿到列号信息,这部分对应后续的 source map 非常有用。
预留提供列号字段。

通用 catch 方案的思考

对于 JavaScript 来说,catch 到的异常信息比 window.onerror 捕获的异常要丰富、有用的多:

window.onerror try/catch
异常堆栈信息
异常位置准确性
异常信息准确性
对源码的影响
对业务逻辑的影响

总体来说

  • try/catch 可以获得更准确有效的异常信息。
    • 但是 catch 住异常,对正常的业务逻辑有较大影响;
    • 重新掷出异常,会导致一个异常被多次掷出,多次被监控。
    • try/catch 有一定的性能问题。
    • tyr/catch 整个模块,对其中的异步代码无效。
  • window.onerror 对业务无影响。
    • 但噪音较大,会发现客户端用户脚本等异常。
    • 异常位置和性能有可能不准确。

对于支付宝来说,业务逻辑中功能占的比重较大,catch 住异常,会导致本应该中断的逻辑会继续执行,并最终导致无法挽救的错误。尽管这个异常已经被监控到。

支付宝现有的监控方案主要通过 window.onerror 进行监控,另外还提供了 API 给业务代码中主动 catch 住的异常提供监控支持。

真正重要的是:发现有效异常并重现。

重现需要以下信息:

  • 用户所处的客户端环境,及其环境的扩展安装情况。例如 Windows 补丁包,浏览器插件安装情况。
  • 用户所处的服务环境,登录状态,持卡情况等。
  • 用户行为:点击路径等。
  • 用户身份信息:必要时直接联系客户。

catch 技术方案

对于展现型业务来说,catch 住异常也许是可以接受的。catch 异常的技术手段可以参考以下方案:

  • 运行时给函数添加 try/catch
    • 可以重写模块及其公开方法。
    • 无法重写内部私有方法。
  • 编译源码时静态给函数添加 try/catch
    • 可以重写所有的函数,包括公开方法和私有函数
    • 甚至可以重写到函数内部,而不是包含在函数外部。

监控完整的系统名,及其服务器编号

#23 新增了系统名的监控,但是只有处理后的系统统一名称,没有对应编号。

决定监控完整的系统名,包括机器编号。然后在数据分析时适当处理。

优点:

  • 可以精确找到异常所对应的详细服务器。
  • 可以区分生产环境和预发布环境。

Fix: 排除客户端本地脚本抛出的异常

  • /^file:\/\/\// 特殊的,WebApp 打包到本地的页面是 file 协议。 策略:打包到本地的页面,也通过将 monitor.js 打包到本地,并针对性处理的方式。
  • /^\w:\\/ 例如:C:\windows\xxx
  • /^chrome:\/\// Firefox 扩展
  • /^chrome-extension:\/\// Chrome 扩展
  • /^safari-extension:\/\// Safari 扩展
  • 站外脚本
    • /^https?:\/\/userscripts.org\//

笼统的:

  • CDN 所在域的脚本异常。
  • 主站所在域的脚本异常。
  • 内部开发域的脚本异常。

特殊的:

  • WebApp file: 协议的本地脚本异常。

敏感信息规则调整

  • 银行卡号限制:以 [34569] 开头,可以减少 50% 的随机数和时间戳造成的误判。
  • 隐私保护规则:
    • 身份证 6...4
    • 银行卡 6...4
    • 手机号 3...4

使用事件发布机制以支持扩展自定义数据

包括 #17 中提到的页面 ID,还有其他可能需要用到的非通用数据,比如需要跟服务端 Session 关联的 cookie/SessionID 信息,这些都可以有最佳实践,但每个场景的需要都不相同,不应该内置。

因此考虑提供事件发布机制,用户在需要时,可以扩展自定义数据。

seajs.use("monitor", function(monitor){
  monitor.on("jserror", function(data){
    data.pageid = "{PAGE_ID}";
    data.cookieid = "{COOKIE_ID}";
  });

  monitor.boot();
});

客户端信息中增加是否为兼容模式

兼容模式对脚本引擎影响较大,因此考虑增加浏览器兼容模式信息。

IE 浏览器可以通过比对浏览器和渲染引擎版本得出是否为兼容模式的信息,但其他 Trident 引擎的非 IE 浏览器无法通过这个得知。

兼容模式信息在 clnt 字段之内的最后。考虑到兼容模式是非主流数据,因此只在兼容模式下添加兼容性信息。后续如果需要在 clnt 中附加其他信息再考虑是否补全。

//clnt=device/device_version|os|os_version|browser/browser_version|engine/engine_version|compatible
clnt=pc/-1|windows/6.1|ie/9.0|trident/5.0
clnt=pc/-1|windows/6.1|ie/9.0|trident/3.0|c

同步发送日志到 kcart 服务器

发送到 kcart 服务器,数据仓库可以帮助实现秒级的实时分析及 UV 分析能力,日志存储能力也有所增加。

切换过程中会同步保留原始到 magentmng 系统的日志。

迁移现有使用版本代码到仓库

仓库现在的是很早之前的项目代码与实际使用的不尽相同。

本仓库代码规划为 3个阶段:

  1. 最早的现有代码 v0.1
  2. 支付宝发布使用的各个版本 v1.x
  3. 将来规划的版本 v2.x

增强:调整 monitor.log API,支持对象传参方式

现在的 monitor.log 传参使用固定的参数格式,导致需要扩展参数比较困难。

经过思考决定让 log API 支持传入键值对的参数,可以根据键值指定扩展参数名。
同时兼容旧有传参方式。

增加系统名监控

  • 目前主要通过 URL 反映出来的域名来分『系统』。
  • 下一步准备直接监控真正的系统名。
<meta name="system-name" content="$!systemUtil.hostInfo.name" />
monitor.on("*", function(meta){
  var content = $("meta[name=system-name]").attr("content");
  var sysName = content.split(/[\-\.]/)[0];
  meta.sys = systemName;
});

使用 Google Analytics 分析异常数据

提供某种机制,用以支持可以使用 GA 的事件监控来处理 JavaScript 异常的数据。

例如

monitor.on("jserror", function(meta){
  _gaq.push(['_trackEvent', meta.message, 'jserror', meta.file + ":" + meta.line]);
  return false; // 取消默认的日志监控行为。
});

注:据说 GA 有 500 个事件的限制。

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.