脚本错误量极致优化-监控上报与Script error

AngK62 8年前
   <p>在前端开发工作中,除了项目开发保质保量上线以外,项目的数据监控也应该配套起来,确保线上的正常运转。如上报 pv 监控项目是否正常运转;测速上报反应项目质量;脚本错误监控作为监控中重要一环,当页面发生报错的时候,通过上报错误信息,能及时发现存在问题,修复优化、减少损失。</p>    <p>本文基于在手Q家校群前端脚本错误量优化的方案,致力于打造极致的脚本错误优化。</p>    <p>作为首篇,主要讲解基础的脚本错误监控和上报方式,以及常会遇到的 Script error. 的产生原因和处理方法。</p>    <h2>监控上报</h2>    <p>脚本错误主要有两类:语法错误、运行时错误。</p>    <p>监控的方式主要有两种:try-catch、window.onerror。</p>    <h2>监控方式</h2>    <h3>示例 · try-catch</h3>    <pre>  <code class="language-javascript">try {      test  // <- throw error  } catch(e){      console.log('运行时错误信息 ↙');      console.log(e);  }</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ae5617290cee58c34d9a708f1424ea72.png"></p>    <p>通过给代码块进行 try-catch 包装,当代码块出错时 catch 将能捕获到错误信息,页面也将继续执行。</p>    <p>当发生语法错误或异步错误时,则无法正常捕捉。</p>    <h3>示例 · try-catch (语法报错)</h3>    <pre>  <code class="language-javascript">try {      function empty()   // <-  throw error 语法错误  } catch(e){      console.log('语法错误信息 ↙');      console.log(e);  }</code></pre>    <p>无法捕捉错误</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a33f63d61338a8658652f36724cd38af.png"></p>    <h3>示例 · try-catch (异步错误)</h3>    <pre>  <code class="language-javascript">try {      setTimeout(function() {          test // <- throw error 异步错误      },0)  } catch(e){      console.log('异步错误信息 ↙');      console.log(e);  }</code></pre>    <p>无法捕捉错误</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2772efaa7d8f724ea13dff43a74233b2.png"></p>    <p>语法错误无法在 try-catch 中进行捕抓、而异步报错则可以通过为异步函数块再包装一层 try-catch,增加标识信息来配合定位,可以用工具来进行处理,这里不展开。</p>    <h3>示例 · window.onerror</h3>    <pre>  <code class="language-javascript">/**   * @param {String}  msg    错误信息   * @param {String}  url    出错文件   * @param {Number}  row    行号   * @param {Number}  col    列号   * @param {Object}  error  错误详细信息   */  window.onerror = function (msg, url, row, col, error) {      console.log('onerror 错误信息 ↙');      console.log({          msg,  url,  row, col, error      })  };    test // <-  throw error</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c2f8380549d6e2cad01cd22def8c8b8e.png"></p>    <p>window.onerror 能捕捉到当前页面的语法错误或运行时报错,是十分强大的。</p>    <p>那么try-catch 是否不再需要呢?其实并不是。</p>    <p>在使用过程中的体会:onerror 主要用来捕获预料之外的错误,而 try-catch 则可以用在预知情况下监控特定错误,两种形式结合使用更加高效。</p>    <h2>上报方式</h2>    <p>监控错误拿到了报错信息,接下来则是将捕抓的错误信息发送到信息收集平台上,发送的形式主要有两种:</p>    <ol>     <li>通过Ajax发送数据</li>     <li>动态创建 img 标签的形式</li>    </ol>    <h3>示例 · 动态创建 img 标签进行上报</h3>    <pre>  <code class="language-javascript">function report(msg, level) {      var reportUrl = "http://localhost:8055/report";      new Image().src = reportUrl + '?msg=' + msg;  }</code></pre>    <h2>监控上报整体流程</h2>    <p>监控报错,并将捕捉到的错误信息上报给数据收集平台,如下图</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/974f4410c7c2a8bcc2b26c2bdcf2f64d.png"></p>    <h2>错误信息分析 · Script error</h2>    <p>有了监控了后,就可以在收集平台上进行查看脚本错误量的日志统计。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/466c758b2e592b75106c6b7ada81b1d5.png"></p>    <p>发现占据榜首的错误信息 “Script error.” 具有非常高的比例,没有无具体的错误信息,无法定位问题,而这是怎么产生的呢?</p>    <h2>产生 Script error 的原因</h2>    <p>翻看在 webkit 的源码 可以看到 “Script error.” 是浏览器在同源策略限制下所产生的。浏览器出于安全上的考虑,当页面引用的非同域的外部脚本中抛出了异常,此时本页面无权限获得这个异常详情, 将输出 Script error 的错误信息。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fc3941d89716f96374576d0426ff4ccb.png"></p>    <h2>优化 Script error</h2>    <p>Script error 来自同源策略的影响,那么解决的方案之一是进行资源的同源化,另外也可以利用跨源资源共享机制( CORS )。</p>    <h3>方案一:同源化</h3>    <ol>     <li>将js代码内联到html文件中</li>     <li>将js文件与html文件放到同一域名下</li>    </ol>    <p>以上两种方式能够简单直接地解决问题,但也可能带来其他影响,如内联资源不好利用文件缓存,同域无法充分利用cdn优势等等。</p>    <h3>方案二:跨源资源共享机制( CORS )</h3>    <p>跨源资源共享 ( CORS )机制让Web应用服务器能支持跨站访问控制,从而能够安全地跨站数据传输。主要是通过给请求带上特定头信息,服务器实现了CORS接口,就可以跨源通信,从而能够看到具体报错信息。</p>    <p>1. 为页面上script标签添加crossorigin属性。</p>    <pre>  <code class="language-javascript"><script src="http://127.0.0.1:8077/main.js" crossorigin></script></code></pre>    <p>增加 crossorigin 属性后,浏览器将自动在请求头中添加一个 Origin 字段,发起一个 跨来源资源共享 请求。Origin 向服务端表明了请求来源,服务端将根据来源判断是否正常响应。</p>    <p><img src="https://simg.open-open.com/show/8d5400c39e9bb7ef55cbcb01b8521ed1.png"></p>    <p>2. 响应头中增加 Access-Control-Allow-Origin 来支持跨域资源共享。</p>    <p><img src="https://simg.open-open.com/show/e11c2fc4053c0fc59b215a16c051f8ff.png"></p>    <p>Access-Control-Allow-Origin: * 表示通过该跨域请求,且该资源可以被任意站点跨站访问。而当该资源仅允许来自 <a href="/misc/goto?guid=4959741372121298590" rel="nofollow,noindex">http://127.0.0.1:8066</a> 的跨站请求,其它站点都不能跨站访问时,将可以返回:</p>    <pre>  <code class="language-javascript">Access-Control-Allow-Origin:http://127.0.0.1:8066</code></pre>    <p>3. 指定域名的 Access-Control-Allow-Origin 的响应头中需带上Vary:Origin。</p>    <p>Vary 字段的作用在于为缓存服务器提供缓存规则及缓存筛选的依据。当增加 Vary:Origin 响应头后,缓存服务器将会按照 Origin 字段的内容,缓存不同版本,在请求响应时根据请求头中的 Origin 决定是否能够使用缓存响应。</p>    <p><img src="https://simg.open-open.com/show/4e1d88e75d90be5dc997ec7c0cc907a4.png"></p>    <p>举例 · 不加 Vary 将存在错误命中缓存的问题</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ddb849c6a4dfc13b1b677fa63110807d.png"></p>    <p>上图中,第一个请求(Origin: 127.0.0.1:8066)响应被浏览器缓存了,当第二个请求(Origin: 127.0.0.1:8888)发起,被错误命中了前一个请求的缓存,收到了 Access-Control-Allow-Origin: <a href="/misc/goto?guid=4959741372121298590" rel="nofollow,noindex">http://127.0.0.1:8066</a> 的响应时,将导致资源加载失败。</p>    <p>所以当 Access-Control-Allow-Origin 不是返回为 * 时,需要加上 Vary 返回头来避免引缓存导致的权限问题。</p>    <p>跨域脚本报错产生 Script error. 通过以上方式进行处理后将能够捕获到具体的报错信息了。在 NodeJS 的实现中主要通过添加以下代码:</p>    <pre>  <code class="language-javascript">app.use(function *(next){      // 拿到请求头中的 Origin      var requestOrigin = this.get('Origin');       if (!requestOrigin) { // 不存在则忽略        return yield next;      }        // 设置 Access-Control-Allow-Origin: Origin      this.set('Access-Control-Allow-Origin', requestOrigin);        // 设置 Vary: Origin      this.vary('Origin');      return yield next;  });</code></pre>    <p>以上为本文所有内容,兄弟篇:( coming soon.. )</p>    <p> </p>    <p> </p>    <p>来自:https://github.com/joeyguo/blog/issues/13</p>    <p> </p>